has Power
Kamil Rykowski
What is it?
Open source framework for building mobile cross-platform applications based on Dart.
...more
But why?
do you event lift?
* official support, technical preview
# Download zip from official site
wget <URL>
# Unpack
unzip -a <FILE> -d flutter/
# Add flutter to PATH env
echo export PATH='$PATH:<PATH>/bin' >>
~/.bash_profile
# Done, flutter is ready
flutter create <PROJECT_NAME>
Install & create
Start small
// main.dart
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Text('Flutter has power'),
);
}
}
FOOdy - mock my lunch
FOOdy - mock my lunch
Trust me - we'll use them
# pubspec.yaml
dependencies:
# ...
provider: ^3.0.0+1
badges: ^1.1.0
dio: ^2.1.13
# bash
flutter pub get
# or directly from Android Studio / VSCode
Just configuration
import 'package:flutter/material.dart';
import 'ui/screens/home.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter has Power',
home: HomeScreen(),
);
}
}
Not so clean architecture
as a data containers
import 'package:flutter/widgets.dart';
class Restaurant {
final int id;
final String name;
final String image;
Restaurant({
@required this.id,
@required this.name,
@required this.image,
});
}
as a data providers
List<Restaurant> _restaurants = [
Restaurant(
id: 1,
name: "McDonald",
image: "https://hmp.me/cn9k",
),
// ...
];
List<Restaurant> getRestaurants() {
return _restaurants;
}
Screen is just a Widget
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text("Hello Home!"),
);
}
}
Fundamental layout builder
Widget build(BuildContext context) {
return Scaffold(
appBar: null,
body: Center(
child: Text("Hello Home!")
)
);
}
Sticky header
appBar: AppBar(
backgroundColor: Colors.black,
title: Text("FOOdy"),
actions: [
Padding(
padding: EdgeInsets.only(right: 16),
child: Icon(Icons.shopping_basket),
),
],
)
Render some data
// 1
body: ListView(
padding: EdgeInsets.all(16),
children: [1, 2, 3].map((item) {
return Text("Item $item");
}).toList(),
)
// 2
body: ListView.builder(
padding: EdgeInsets.all(16),
itemCount: 3,
itemBuilder: (context, index) {
return Text("Item $index");
},
)
It's working!
body: ListView(
padding: EdgeInsets.all(16),
children: getRestaurants().map((restaurant) {
return Image.network(
restaurant.image,
height: 170,
fit: BoxFit.cover,
width: double.infinity,
);
}).toList(),
)
Widgets decomposition
class RestaurantItem extends StatelessWidget {
final bool isFavorite;
final Restaurant restaurant;
final Function onPressed;
RestaurantItem({
this.isFavorite,
this.restaurant,
this.onPressed,
});
@override
Widget build(BuildContext context) {
return Text("Single item");
}
}
Overlap the widgets (absolute)
Stack(
children: [
Container(
width: 250,
height: 100,
color: Colors.green,
),
Positioned(
left: 10,
bottom: 10,
child: Text("Restaurant"),
),
],
)
Click-click
IconButton(
onPressed: () => {},
icon: Icon(
Icons.favorite,
color: Colors.red,
size: 20,
),
)
FloatingActionButton(
onPressed: () => {},
backgroundColor: Colors.black,
child: Icon(Icons.favorite),
)
Serious stuff
GestureDetector(
onTap: () {},
onDoubleTap: () {},
onLongPress: () {},
// ... and many more
child: Container(
color: Colors.red,
child: Text("Stop there!"),
),
)
Local scope
class MyWidget extends StatefulWidget {
@override
MyState createState() => MyState();
}
class MyState extends State<MyWidget> {
int clickCount = 0;
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('counter: $clickCount'),
onPressed: () => setState(() {
clickCount++; }),
);
}
}
Manage your data
class HomeScreen extends StatelessWidget {
// ...
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() {
return _HomeScreenState();
}
}
class _HomeScreenState extends State<HomeScreen> {
// ...
}
setState
List<Restaurant> favorites = [];
void toggleFavorite(Restaurant restaurant) {
setState(() {
favorites.contains(restaurant)
? favorites.remove(restaurant)
: favorites.add(restaurant);
});
}
https://my-json-server.typicode.com/vintage/flutter_has_power |
List<Restaurant> getRestaurants() {
return _restaurants;
}
Future<List<Restaurant>> getRestaurants() async {
var response = await Dio().get("URL");
return List<Restaurant>.from(response.data.map((data) {
return Restaurant(
id: data["id"],
name: data["name"],
image: data["image"],
);
});
}
complex widget building
FutureBuilder<List<Restaurant>>(
future: getRestaurants(),
builder: (context, snapshot) {
if (snapshot.connectionState !=
ConnectionState.done) {
return Text("Loading");
}
print(snapshot.data);
return Text("Got it!");
},
);
avoid DDoSing your own API
Future<List<Restaurant>> restaurantsLoader;
@override
void initState() {
restaurantsLoader = getRestaurants();
super.initState();
}
FutureBuilder<List<Restaurant>>(
future: restaurantsLoader,
// ...
);
Ready to l(a)unch
Sticky header
List of items
Stacked layout
Clickable widgets
Local state management
API integration
Handling async code in build
Lifecycle methods
Yet another screen
import 'package:flutter/material.dart';
import '../shared/header.dart';
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: Header(),
body: Center(
child: Text("Detail screen"),
),
);
}
}
Bit tricky
class Header extends StatelessWidget implements
PreferredSizeWidget {
Size get preferredSize {
return new Size.fromHeight(kToolbarHeight);
}
@override
Widget build(BuildContext context) {
return AppBar(...);
}
}
Configure the routes
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// ...
routes: {
'/details': (context) => DetailScreen(),
},
);
}
}
Screen transition
Navigator.of(context).pushNamed("/details");
Navigator.push(context, MaterialPageRoute(
builder: (context) => DetailScreen(),
));
Different animation
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => DetailScreen(),
),
);
Passing arguments
class DetailScreenArguments {
final Restaurant restaurant;
DetailScreenArguments({this.restaurant});
}
Navigator.of(context).pushNamed(
"/details",
arguments: DetailScreenArguments(
restaurant: restaurant,
),
);
Receiving arguments
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final DetailScreenArguments args =
ModalRoute.of(context).settings.arguments;
return Text(args.restaurant.name);
}
}
Row & Column
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text("1"),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("2"),
Text("two"),
Text("double"),
],
),
Text("3"),
],
)
Expected behavior
bundled static files
# pubspec.yaml
# ...
flutter:
uses-material-design: true
assets:
# - assets/menu_empty.jpg
- assets/
access the assets
import 'package:flutter/services.dart' show rootBundle;
Future<String> loadAsset() async {
return await rootBundle.loadString('assets/c.json');
}
Image.asset(
'assets/menu_empty.jpg',
height: 80,
width: 80,
fit: BoxFit.cover,
)
Ready to l(a)unch
Navigation between screens
Passing arguments during navigation
Grid layout with Row and Column
Adding static files (assets) to the app
Displaying image from local asset
Final screen
class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("Cart screen"),
),
);
}
}
Do It Yourself
Stack(
children: [
Center(child: Text("Main content")),
Positioned(
top: 10,
right: 10,
child: FloatingActionButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Icon(Icons.close),
),
)
],
)
Do It Yourself
Global scope via provider
import 'package:flutter/material.dart';
import './models.dart';
class CartState extends ChangeNotifier {
Map<Menu, int> _items = {};
Map<Menu, int> get items => _items;
void addItem(Menu item) {
_items.putIfAbsent(item, () => 0);
_items[item] += 1;
notifyListeners();
}
int get count => _items.values.fold(0, (a, b) => a + b);
}
Provide value down the tree
void main() {
return runApp(
ChangeNotifierProvider<CartState>.value(
value: CartState(),
child: App(),
),
);
}
Many providers
void main() {
return runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(builder: (context) {
return CartState();
}),
ChangeNotifierProvider(builder: (context) {
return UserState();
}),
],
child: App(),
),
);
}
Access stored data
Consumer<CartState>(
builder: (context, value, _) => Text(
value.count.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 12,
),
),
)
Provider.of<CartState>(context).addItem(menu);
Real grid
GridView.count(
padding: EdgeInsets.all(8),
crossAxisSpacing: 4,
mainAxisSpacing: 4,
crossAxisCount: 3,
scrollDirection: Axis.vertical,
children: items.keys.map((menu) {
return Text(menu.name);
}).toList(),
);
Easy & lovely
Widget proportions
Column(
children: [
Expanded(
child: Container(
color: Colors.black,
),
),
Text("Bottom"),
],
);
Relative positioning
Ready to l(a)unch
Hand crafted modal alike screen
Global state management with provider
Expanded widget
Real grid view
Finalize the app
Flutter has Power
Kamil Rykowski