Skip to content

Instantly share code, notes, and snippets.

@lukas-h
Last active December 21, 2023 13:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lukas-h/6bed111dfa9aafdf87453ba2fc93415e to your computer and use it in GitHub Desktop.
Save lukas-h/6bed111dfa9aafdf87453ba2fc93415e to your computer and use it in GitHub Desktop.
Flutter state management workshop Dec '23
void main() {
// data structures
// synchronous
int v = 1; // single value
List<int> l = [1,2,3]; // multiple values
// asynchronous
final f = Future<int>.value(1); // single value
final s = Stream<int>.fromIterable([1,2,3]); // multiple values
}
import 'dart:async';
Future<void> main() async {
print('Before');
await Future.delayed(const Duration(seconds: 3));
print('After');
final resp = await getDataFromAPI();
print(resp);
}
Future<String> getDataFromAPI() async {
// API HTTP Call
await Future.delayed(const Duration(seconds: 3));
return 'RESPONSE PAYLOAD';
}
import 'dart:async';
Future<void> main() async {
print('>>> LISTS:');
final list = [1, 2, 3, 4]; // list / array
list.add(5);
list.removeAt(list.length - 1);
final doubledList = list.map((e) => e * 2).toList(); // [2, 4, 6, 8]
for (final item in list) {
print(item);
}
// remember: Lists are synchronous
print('>>> STREAMS:');
final controller = StreamController<int>.broadcast();
final stream = controller.stream;
controller.add(1);
Future.delayed(Duration(seconds: 3)).then((_) {
controller.add(2);
});
Future.delayed(Duration(seconds: 4)).then((_) {
controller.add(3);
});
Future.delayed(Duration(seconds: 5)).then((_) {
controller.addStream(Stream<int>.fromIterable([4, 5, 6]));
});
final mappedStream = stream.map((e) => e * 2);
await for (final item in stream) {
print(item);
}
}
import 'dart:async';
Future<int> getInt() async {
await Future.delayed(const Duration(seconds: 1));
return 1;
}
Stream<int> getStreamOfInts() async* {
int counter = 1;
while (true) {
await Future.delayed(const Duration(seconds: 1));
yield counter++;
}
}
Future<void> main() async {
final myInt = await getInt();
print(myInt);
final myIntStream = getStreamOfInts();
myIntStream.listen(print);
}

State Management in Flutter

What is "state"?

Also known "UI state".

https://docs.flutter.dev/data-and-backend/state-mgmt/ephemeral-vs-app

E.g. StatefulWidget –> widgets containing a state –> state change is triggered via setState

What are "events"?

Events are actions that trigger a state change.

Example:

  1. State: the screen shows a button
  2. Event: the button is clicked
  3. New state: A new screen is opened

What is "state management"?

What is "unidirectional dataflow"?

With https://bloclibrary.dev/#/

import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: CounterWidget(),
);
}
}
class CounterWidget extends StatefulWidget {
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'Counter: $counter',
style: Theme.of(context).textTheme.headlineMedium,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
counter++;
});
},
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder(
future: Future.delayed(Duration(seconds: 2)),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.connectionState == ConnectionState.done) {
return Text('Future completed');
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
Stream<int> getIntStream() async* {
int counter = 0;
await Future.delayed(Duration(seconds: 3));
while (true) {
await Future.delayed(Duration(seconds: 1));
yield counter++;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder(
stream: getIntStream(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
return Text('New state with value: ${snap.data}');
} else {
return CircularProgressIndicator();
}
},
),
),
);
}
}
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LayoutBuilder(
builder: (BuildContext context,BoxConstraints constraints) {
return Text('Height: ${constraints.maxHeight}, Width: ${constraints.maxWidth}');
},
),
),
);
}
}
import 'package:bloc/bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
void main() {
final cubit = CounterCubit();
print(cubit.state); // 0
cubit.increment();
print(cubit.state); // 1
cubit.increment();
print(cubit.state); // 2
cubit.close();
}
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
);
}
}
class MyWidget extends StatelessWidget {
final cubit = CounterCubit();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<CounterCubit, int>(
bloc: cubit,
builder: (BuildContext context, int state) {
return Text('Counter: $state');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
cubit.increment();
},
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
}
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterCubit(),
child: MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
),
);
}
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// read cubit from context
final cubit = context.read<CounterCubit>();
return Scaffold(
body: Center(
// BlocBuilder internally get the cubit from the context
child: BlocBuilder<CounterCubit, int>(
builder: (BuildContext context, int state) {
return Text('Counter: $state');
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
//
cubit.increment();
},
child: Icon(Icons.add),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class NameCubit extends Cubit<String> {
NameCubit() : super('');
void addNewName(String name) => emit(name);
}
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => NameCubit(),
child: MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget(),
),
);
}
}
class MyWidget extends StatelessWidget {
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
// read cubit from context
final cubit = context.read<NameCubit>();
return Scaffold(
body: Center(
// BlocBuilder internally get the cubit from the context
child: BlocListener<NameCubit, String>(
listener: (BuildContext context, String state) {
if (state.isNotEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Hello, $state!'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('OK'),
),
],
),
);
}
},
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Enter your name'),
),
),
ElevatedButton(
onPressed: () {
cubit.addNewName(_controller.text.trim());
_controller.clear();
},
child: Text('Submit'),
),
],
),
),
),
),
);
}
}

More things to learn with bloclibrary.dev

  • MultiBlocProvider – to add multiple blocs to the context
  • BlocListener – which works like BlocBuilder but doesn't return a widget
  • BlocConsumer – which contains a builder and listener function. Basically BlocBuilder + BlocListener combined

Things for the next workshop

  • More advanced Cubit examples
  • Repository and RepositoryProvider
  • Stateful Repository pattern
  • BloC testing
  • Layered architecture (data, domain, presentation (logic + UI) layers)
  • Feature-based architecture
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment