0% found this document useful (0 votes)
18 views64 pages

03-Module 3

Uploaded by

Habiba Yasser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
18 views64 pages

03-Module 3

Uploaded by

Habiba Yasser
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 64

CSE431

Mobile Programming

MODULE 3:
BUILDING USER INTERFACES WITH
FLUTTER
Module 3
• Introduction to UI Design
• Introduction to Basic Flutter Widgets
• UI Styles: Declarative style vs Imperative style
• Comparison between Flutter's Scaffold and Android’s Native
Activities/Fragments
• Flutter Layout Widgets
– Single-child Layout Widgets
– Multi-child Layout Widgets
• Aligning Widgets
• Designing Your Layout in Flutter
• Navigation and routing
• Options to Pass Data Between Screens in Flutter
• CodeLab: Creating layouts in Flutter

Reference: https://docs.flutter.dev/ui
2
Introduction to UI Design

3
UI Design
Introduction

▪ In Flutter, everything you see on the screen is a widget

▪ Widgets follow a hierarchical structure, where parent widgets


contain child widgets to build the overall interface.

▪ Unlike other frameworks, Flutter uses Dart for both App functions
and UI

4
In Flutter, the main root of an application is often a
StatelessWidget even if there are parts of the
UI Design widget tree that need to update (like changing text).
This is possible because of Flutter's architecture,
Widgets which separates the UI description from the state
management.

https://docs.utter.dev/reference/widgets

5
Stateless Widgets Accessibility Value Widgets
do not contain
states hence they
can be updated
Animation and Motion Layout widgets
only when its
parent changes.
Assets, Images, and Icons Navigation widgets
Once created,
stateless widgets Async Other Widgets
Stateless vs Stateful

Based on Functions
Classic Categories
cannot be updated
means they are Basic Widgets
immutable, they
have to be created Input
again by supplying
new data in order Cupertino
to see the
changes. Interaction Models
Stateful
Widgets can hold Layout
the states
internally, so it can Painting and Effects
be updated
whenever its Scrolling
states changes and
also whenever its Styling
parent changes. It
is mutable widget,
so it can be drawn
Text
multiple times in
its lifetime.

6
https://docs.utter.dev/reference/widgets
Lifecycle of Widgets

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 7
Lifecycle of Widgets
What are Widget
Lifecycle Methods? createState()

The widget lifecycle is a


sequence of events
that occur when a
widget is created,
updated, or destroyed.
Understanding the
widget lifecycle is
important for writing
efficient Flutter
applications.

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 8
Widget Lifecycle Methods

createState()
initState()
didChangeDependencies()
build()
didUpdateWidget()
setState()
deactivate()
dispose()

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 9
createState()

This method creates the state object for the widget.


When we create a stateful widget, our framework calls
a createState() method and it must be overridden.

class MyPage extends StatefulWidget {


@override
_MyPageState createState() => _MyScreenState();
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 10
initState()

This method is called after the state object is created.


It is used to initialize the state of the widget.

late int _counter;


@override
void initState() {
print("initState");
_counter = 0;
super.initState();
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 11
build()
This method is called after the state object is initialized. It is used to build the
widget tree. This gets called each time the widget is rebuilt this can happen after
initState, didChangeDependencies, didUpdateWidget, or when the state is
changed via a call to setState.
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
appBar: AppBar(
title: const Text("Lifecycle Demo"),
),
body: Container(
child: Column(
children: [
Text(_counter.toString()),
ElevatedButton(onPressed: _increment, child: const Text("Increment"))
],
)),
);
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 12
didChangeDependencies()

This method is called immediately after initState and


when dependency of the State object changes via
InheritedWidget*.

@override
void didChangeDependencies() {
print("didChangeDependencies");
super.didChangeDependencies();
}

*Base class for widgets that efficiently propagate information down the tree.

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 13
didUpdateWidget()

This method is called when the widget is updated with


new properties. A typical case is when a parent passes
some variable to the children() widget via the
constructor.

@override
void didUpdateWidget(covariant MyPage oldWidget)
{
print("didUpdateWidget");
super.didUpdateWidget(oldWidget);
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 14
deactivate()

This method is invoked whenState is removed from


subtree A and reinserted to subtree B with the use of
a GlobalKey.

@override
void deactivate() {
print("deactivate");
super.deactivate();
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 15
dispose()

This method is called when the widget is about to be


destroyed permanently. It is used to release any
resources used by the widget like closing network
connections or stopping animations.

@override
void dispose() {
print("dispose");
super.dispose();
}

https://medium.com/gytworkz/flutter-widget-lifecycle-everything-you-need-to-know-629d01ca4a09 16
Basic Widgets

17
UI Design
Basic widgets

▪ Flutter comes with a suite of powerful basic widgets, of which the


following are commonly used:

Text

•The Text widget lets you create a run of styled text within your application.

Row, Column

•These flex widgets let you create flexible layouts in both the horizontal (Row) and vertical
(Column) directions. The design of these objects is based on the web's flexbox layout
model.

Stack

•Instead of being linearly oriented (either horizontally or vertically), a Stack widget lets
you place widgets on top of each other in paint order. You can then use the Positioned
widget on children of a Stack to position them relative to the top, right, bottom, or left
edge of the stack. Stacks are based on the web's absolute positioning layout model.

Container

•The Container widget lets you create a rectangular visual element. A container can be
decorated with a BoxDecoration, such as a background, a border, or a shadow. A
Container can also have margins, padding, and constraints applied to its size. In addition,
a Container can be transformed in three-dimensional space using a matrix.

18
UI Design
Using Material Components

▪ Flutter provides a number of widgets that help you build apps that
follow Material Design.

▪ A Material app starts with the MaterialApp widget, which builds a


number of useful widgets at the root of your app, including a
Navigator, which manages a stack of widgets identified by strings,
also known as "routes".

▪ The Navigator lets you transition smoothly between screens of


your application.

▪ Using the MaterialApp widget is entirely optional but a good


practice.

19
UI Design
Using Material Components
import 'package:flutter/material.dart'; class MyScaffold extends StatelessWidget {
const MyScaffold({super.key});
class MyAppBar extends StatelessWidget {
const MyAppBar({required this.title, super.key}); @override
Widget build(BuildContext context) {
// Fields in a Widget subclass are always marked "final". // Material is a conceptual piece
// of paper on which the UI appears.
final Widget title; return Material(
// Column is a vertical, linear layout.
@override child: Column(
Widget build(BuildContext context) { children: [
return Container( MyAppBar(
height: 56, // in logical pixels title: Text(
padding: const EdgeInsets.symmetric(horizontal: 8), 'Example title',
decoration: BoxDecoration(color: Colors.blue[500]), style: Theme.of(context) //
// Row is a horizontal, linear layout. .primaryTextTheme
child: Row( .titleLarge,
children: [ ),
const IconButton( ),
icon: Icon(Icons.menu), const Expanded(
tooltip: 'Navigation menu', child: Center(
onPressed: null, // null disables the button child: Text('Hello, world!'),
), ),
// Expanded expands its child ),
// to fill the available space. ],
Expanded( ),
child: title, );
), }
const IconButton( }
icon: Icon(Icons.search),
tooltip: 'Search', void main() {
onPressed: null, runApp(
), const MaterialApp(
], title: 'My app', // used by the OS task switcher
), home: SafeArea(
); child: MyScaffold(),
} ),
} ),
);
}

20
UI Design import 'package:flutter/material.dart';

Handling gestures class MyButton extends StatelessWidget {


const MyButton({super.key});

@override
Most applications include some form of user Widget build(BuildContext context) {
return GestureDetector(
interaction with the system. The first step in building onTap: () {
an interactive application is to detect input gestures. },
print('MyButton was tapped!');

See how that works by creating a simple button: child: Container(


height: 50,
padding: const EdgeInsets.all(8),
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.lightGreen[500],
),
child: const Center(
child: Text('Engage'),
),
),
);
}
}

void main() {
runApp(
const MaterialApp(
home: Scaffold(
body: Center(
child: MyButton(),
),
),
),
);
}
21
UI Design
Changing widgets in response to input
▪ In order to build more complex experiences—for example, to react in more interesting ways to
user input—applications typically carry some state.
▪ Flutter uses StatefulWidgets to capture this idea.
▪ StatefulWidgets are special widgets that know how to generate State objects, which are then
used to hold state. Consider this basic example, using the ElevatedButton mentioned earlier:

import 'package:flutter/material.dart'; @override


Widget build(BuildContext context) {
class Counter extends StatefulWidget { // This method is rerun every time setState is called,
// This class is the configuration for the state. // for instance, as done by the _increment method above.
// It holds the values (in this case nothing) provided // The Flutter framework has been optimized to make
// by the parent and used by the build method of the // rerunning build methods fast, so that you can just
// State. Fields in a Widget subclass are always marked // rebuild anything that needs updating rather than
// "final". // having to individually changes instances of widgets.
return Row(
const Counter({super.key}); mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
@override ElevatedButton(
State<Counter> createState() => _CounterState(); onPressed: _increment,
} child: const Text('Increment'),
),
class _CounterState extends State<Counter> { const SizedBox(width: 16),
int _counter = 0; Text('Count: $_counter'),
],
void _increment() { );
setState(() { }
// This call to setState tells the Flutter framework }
// that something has changed in this State, which
// causes it to rerun the build method below so that void main() {
// the display can reflect the updated values. If you runApp(
// change _counter without calling setState(), then const MaterialApp(
// the build method won't be called again, and so home: Scaffold(
// nothing would appear to happen. body: Center(
_counter++; child: Counter(),
}); ),
} ),
),
); 22
}
UI Design
Responding to widget lifecycle events
▪ After calling createState() on the StatefulWidget, the framework inserts
the new state object into the tree and then calls initState() on the state
object.

▪ A subclass of State can override initState to do work that needs to


happen just once.

▪ For example, override initState to configure animations or to subscribe to


platform services. Implementations of initState are required to start by
calling super.initState.

▪ When a state object is no longer needed, the framework calls dispose()


on the state object.

▪ Override the dispose function to do cleanup work. For example, override


dispose to cancel timers or to unsubscribe from platform services.
Implementations of dispose typically end by calling super.dispose.

23
Flutter has a
collection of visual,
structural, platform,
and interactive
widgets.

https://docs.flutter.dev/ui/widgets

24
UI Styles: Declarative style
vs Imperative style

25
UI Styles
Declarative style vs Imperative style

• Declarative Style (Flutter)


• In a declarative UI framework, the developer focuses on what the UI
should look like at any given moment based on the current state.

• The UI is essentially a function of the state:


• every time the state changes, the framework rebuilds the UI to reflect
those changes. This approach emphasizes simplicity and clarity in
describing the desired result.

• In an imperative UI framework, the developer explicitly instructs the


framework on how to update the UI in response to specific actions or
state changes.

• The focus is on managing the sequence of operations that modify the UI,
with the developer directly controlling each UI update step.

26
UI Styles
Declarative style vs Imperative style

27
Comparison between Flutter's
Scaffold and Android’s Native
Activities/Fragments

28
UI Design
Flutter's Scaffold and Android’s Native Activities/Fragments

▪ UI Structure Management

▪ Handling Multiple Components

▪ Layout and Design

▪ State Management

▪ Navigation

29
Layout Widgets: Single-child
& Multi-child Widgets

30
Layouts in Flutter
Everything is a Widget!

▪ The core of Flutter's layout


mechanism is widgets. In
Flutter, almost everything is
a widget—even layout
models are widgets.

▪ The images, icons, and text


that you see in a Flutter app
are all widgets.

▪ But things you don't see are


also widgets, such as the
rows, columns, and grids
that arrange, constrain, and
align the visible widgets.

31
Layout widgets

Single-child Multi-child
layout widgets layout widgets

Sliver widgets

32
Layout widgets

33
Layout widgets

34
Layout widgets

35
Aligning Widgets

36
Lay out multiple widgets vertically and horizontally

37
Lay out multiple widgets vertically and horizontally

38
Aligning widgets

• You control how a row or column aligns its children using the mainAxisAlignment
and crossAxisAlignment properties.
• For a row, the main axis runs horizontally and the cross axis runs vertically.
• For a column, the main axis runs vertically and the cross axis runs horizontally.

39
Aligning widgets

• When you add images to your project, you need to update the pubspec.yaml file to
access them—this example uses Image.asset to display the images. You don't need
to do this if you're referencing online images using Image.network.

Row(
mainAxisAlignment:
MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);

40
Designing Your Layout in
Flutter

41
UI Design
Designing Your Layout in Flutter

• Can you identify the rows and columns in the design?

• Does your layout involve a grid structure?

• Are there any overlapping elements?

• Does the UI require tabs?

• What needs alignment, padding, or borders?

42
Navigation and routing

43
The Navigator widget displays screens as a stack using
the correct transition animations for the target
platform. To navigate to a new screen, access the
Using the
Navigator through the route's BuildContext and call Navigator
imperative methods such as push() or pop() 44
Flutter applications with advanced navigation and routing
requirements (such as a web app that uses direct links to each screen,
or an app with multiple Navigator widgets) should use a routing
Using the
package such as go_router that can parse the route path and
configure the Navigator whenever the app receives a new deep link.
Router
45
Options to Pass Data Between
Screens in Flutter

46
Ex01: Using Constructor Arguments

import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navigation Example',
home: HomeScreen(),
);
}
}

47
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigating and passing data
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailScreen(data: 'Hello World')),
);
},
child: Text('Go to Detail Screen'),
),
),
);
}
}
48
class DetailScreen extends StatelessWidget {
final String data;

DetailScreen({required this.data});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar AppBar(
title Text('Detail Screen'),
),
body Center(
child Text(data, style TextStyle(fontSize 24)),
),
);
}
}

49
Ex02: Using Navigator.push and
Navigator.pop
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Data Passing Example',
home: HomeScreen(),
);
}
}

50
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
// Navigating and retrieving data
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
print(result); // Outputs: Selected Data
// Show result in a SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Result: $result')),
);
},
child: Text('Go to Second Screen'),
),
),
);
} 51
}
// Second screen returning data
class SelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Selection Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, 'Data = 55');
},
child: Text('Send Data to first screen'),
),
),
);
}
}

52
Ex03: Using Named Routes with Arguments
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Named Routes Example',
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
},
);
}
} 53
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigating with arguments
Navigator.pushNamed(
context,
'/details',
arguments: 'Passed Data',
);
},
child: Text('Go to Detail Screen'),
),
),
);
}
} 54
// Retrieving data in the new screen
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final String data = ModalRoute.of(context)!.settings.arguments as String;

return Scaffold(
appBar: AppBar(
title: Text('Detail Screen'),
),
body: Center(
child: Text(
data,
style: TextStyle(fontSize: 24),
),
),
);
}
}

Modify this code to send multiple arguments ?


55
Ex04: Using State Management Solutions
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// ChangeNotifier to hold shared data


class Counter extends ChangeNotifier {
int _count = 0;

int get count => _count; class MyApp extends StatelessWidget {


@override
void increment() {
_count++;
Widget build(BuildContext context) {
notifyListeners(); return MaterialApp(
} title: 'Provider Example',
}
home: HomeScreen(),
// Providing the Counter globally );
void main() { }
runApp( }
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
56
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
child: Text('Go to Second Screen'),
),
),
);
}
}
57
// Second screen accessing shared data
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text(
'Count: ${counter.count}',
style: TextStyle(fontSize: 24),
),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: counter.increment,
child: Text('Increment'),
),
],
),
);
}
}
58
Ex05: Using Global Variables or Singletons
import 'package:flutter/material.dart';

// Singleton class to hold shared data


class DataStore {
static final DataStore _instance = DataStore._internal();
String sharedData = 'Initial Data';

factory DataStore() => _instance;

DataStore._internal();
}

// Main function to run the app


void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {


@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Singleton Example',
home: HomeScreen(),
);
59
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Screen'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Navigate to the second screen
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
child: Text('Go to Second Screen'),
),
),
);
}
}
60
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Accessing shared data
DataStore store = DataStore();

return Scaffold(
appBar: AppBar(
title: Text('Second Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Shared Data: ${store.sharedData}',
style: TextStyle(fontSize: 24),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
// Update the shared data
store.sharedData = 'Updated Data';
// Optionally navigate back
Navigator.pop(context);
},
child: Text('Update Data and Go Back'),
),
],
),
),
);
}
}
61
CodeLab: Creating layouts in
Flutter

62
Your Second Flutter app

https://dartpad.dev/?id=e7076b40fb17a0fa899f9f7a154a02e8

63
End of Module 3

You might also like