(live coding)
A list is an indexable collection of objects with a length (an array).
The most common kinds of lists are:
Fixed-length list
Growable list
There are various widgets in Flutter that accept a list of widgets as their children
Column(
children: <Widget>[
Text('Deliver features faster'),
Text('Craft beautiful UIs'),
Expanded(
child: FittedBox(
fit: BoxFit.contain, // otherwise the logo will be tiny
child: const FlutterLogo(),
),
),
],
)
Creates a scrollable, linear array of widgets that are created on demand.
This constructor is appropriate for list views with a large (or infinite) number of children because the builder is called only for those children that are actually visible.
ListView.separated(
itemCount: todos.length,
itemBuilder: (context, index) {
var todo = todos[index];
return Container(
child: Text(todo),
padding: EdgeInsets.all(10),
);
},
separatorBuilder: (context, index) => Container(
height: 1,
color: Color.fromRGBO(240, 240, 240, 1),
),
),
List<String> todos = [
'Do laundry',
'Buy Christmas presents',
'Vacuum the floor',
];
The provider package is a mixture between dependency injection and state management, built with widgets for widgets.
Provider<Foo>(create: (_) => Foo())
MultiProvider(
providers: [
Provider<Foo>(create: (_) => Foo()),
Provider<Bar>(create: (_) => Bar()),
],
child: someWidget,
)
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
create: (_, counter, __) => Translations(counter.value),
),
import 'package:flutter/foundation.dart';
class TodoProvider with ChangeNotifier {
List<String> _todos = [
'Do laundry',
'Buy Christmas presents',
'Vacuum the floor',
];
List<String> get todos {
return _todos;
}
addTodo(data) {
_todos.add(data);
notifyListeners();
}
removeTodo(data) {
_todos.removeWhere((todo) => todo == data);
notifyListeners();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/todo_provider.dart';
Widget build(BuildContext context) {
var provider = Provider.of<TodoProvider>(context);
var todos = provider.todos;
return Center(...),
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/todo_provider.dart';
class ExampleConsumer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')),
body: Consumer<TodoProvider>(
builder: (context, provider, child) {
return ListView.separated(
itemCount: provider.todos.length,
itemBuilder: (context, index) => ...,
separatorBuilder: ...,
);
},
),
);
}
}
The package information is located in the pubspec.yaml file. Just add the new dependency and your IDE should download it automatically.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
provider: 4.0.4
import 'package:flutter/foundation.dart';
class TodoProvider with ChangeNotifier {
List<String> _todos = [
'Do laundry',
'Buy Christmas presents',
'Vacuum the floor',
];
List<String> get todos {
return _todos;
}
addTodo(data) {
_todos.add(data);
notifyListeners();
}
removeTodo(data) {
_todos.removeWhere((todo) => todo == data);
notifyListeners();
}
}
lib/providers/todo_provider.dart
Don't forget to notify the listeners when you want to react to a change in a provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './todo_provider.dart';
void main() {
return runApp(
ChangeNotifierProvider(child: MyApp(), create: (_) => TodoProvider()),
);
}
Mobile apps typically reveal their contents via full-screen elements called "screens" or "pages".
In Flutter these elements are called routes and they're managed by a Navigator widget. The navigator manages a stack of Route objects and provides methods for managing the stack, like Navigator.push and Navigator.pop.
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('My Page')),
body: Center(
child: FlatButton(
child: Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));
void main() {
runApp(MaterialApp(
home: MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/users': (BuildContext context) => MyPage(title: 'Users'),
'/profile': (BuildContext context) => MyPage(title: 'Profile'),
},
));
}
main.dart
Navigator.of(context).pushNamed('/todos');
Navigator.of(context).pushNamed('/todo', arguments: {'id': 1});
You can simply push a named route without arguments, for example /todos for Todo list
You can also provide any arguments for the component to use
class ExtractArgumentsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var args = ModalRoute.of(context).settings.arguments;
return Scaffold(...);
}
}
import 'package:flutter/material.dart';
class TodoList extends StatelessWidget {
List<String> todos = [...];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My todos'),
),
body: ListView.separated(
itemBuilder: (context, index) {
var todo = todos[index];
return Container(
child: Text(todo),
padding: EdgeInsets.all(10),
);
},
separatorBuilder: (context, index) {
return Container(
height: 1,
color: Color.fromRGBO(240, 240, 240, 1),
);
},
itemCount: todos.length,
),
);
}
}
screens/todo_list_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './providers/todo_provider.dart';
import './screens/todo_list.dart';
void main() => runApp(...);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo app',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TodoList(),
);
}
}
main.dart
Widget build(BuildContext context) {
var provider = Provider.of<TodoProvider>(context);
var todos = provider.todos;
return Scaffold(...);
}
screens/todo_list_screen.dart
import 'package:flutter/material.dart';
class AddTodoScreen extends StatelessWidget {
static const routeName = '/add';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('New todo'),
),
);
}
}
screens/add_todo_screen.dart
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.of(context).pushNamed(AddTodoScreen.routeName);
},
),
screens/todo_list_screen.dart
home: TodoList(),
routes: {
AddTodoScreen.routeName: (_) => AddTodoScreen(),
},
main.dart
TextField(
decoration: InputDecoration(
hintText: 'Add a todo',
),
onChanged: (String value) {
// set the value
},
)
There are various ways to handle forms in flutter - using Form() widget, TextField() and TextFormField()
import 'package:flutter/material.dart';
class AddTodoScreen extends StatelessWidget {
static const routeName = '/add';
var _todoController = TextEditingController(text: '');
@override
Widget build(BuildContext context) {
return TextField(
decoration: InputDecoration(
hintText: 'Add a todo',
),
controller: _todoController,
);
}
}
You can create a new TextEditingController and provide it as an argument for the TextField() - updating the value won't refresh the widget.
If you wish to monitor the changes, you can attach a listener to the controller.
FlatButton(
onPressed: () => _handleSubmit(context),
child: Text('Save todo'),
color: Colors.blue,
),
void _handleSubmit(BuildContext context) {
var newTodo = _todoController.value.text;
var provider = Provider.of<TodoProvider>(context, listen: false);
provider.addTodo(newTodo);
Navigator.of(context).pop();
}
Access the properties .value.text on your controller variable to get the current value inside it
Remove the current route from the route stack
GestureDetector(
onTap: _handleTap,
child: Container(
child: Text(todo),
padding: EdgeInsets.all(10),
),
);
Whenever you want to react to user gestures, there's the GestureDetector widget, providing you:
Material(
child: InkWell(
onTap: _handleTap,
child: Container(
child: Text(todo),
padding: EdgeInsets.all(10),
),
),
);
Assuming InkWell is wrapped in a Material styled widget (Material, Container..), it provides user with a nice tap animation
import 'package:flutter/material.dart';
class TodoDetailScreen extends StatelessWidget {
static const routeName = '/todo-detail';
@override
Widget build(BuildContext context) {
var todo = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: AppBar(title: Text(todo)),
body: Center(
child: Text(
todo,
style: TextStyle(fontSize: 20),
),
),
);
}
}
routes: {
AddTodoScreen.routeName: (_) => AddTodoScreen(),
TodoDetailScreen.routeName: (_) => TodoDetailScreen(),
},
itemBuilder: (context, index) {
var todo = todos[index];
return InkWell(
child: Container(
child: Text(todo),
padding: EdgeInsets.all(10),
),
onTap: () {
Navigator.of(context).pushNamed(
TodoDetailScreen.routeName,
arguments: todo,
);
},
);
},
screens/todo_list_screen.dart
floatingActionButton: FloatingActionButton(
onPressed: () => _handleDelete(context, todo),
child: Icon(Icons.delete),
backgroundColor: Color.fromRGBO(240, 0, 50, 1),
),
screens/todo_detail_screen.dart
void _handleDelete(BuildContext context, String todo) {
var provider = Provider.of<TodoProvider>(context, listen: false);
provider.removeTodo(todo);
Navigator.of(context).pop();
}
This package provides internationalization and localization facilities, including message translation, plurals and genders, date/number formatting and parsing, and bidirectional text.
new DateFormat.yMMMd().format(new DateTime.now())
A powerful Http client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc.
import 'package:dio/dio.dart';
void getHttp() async {
try {
Response response = await Dio().get("http://www.google.com");
print(response);
} catch (e) {
print(e);
}
}
Tune in after the presentation's finished to get the link!