flutter: how are dependencies on inherited widgets discovered? - flutter

I'm currently reading the example code of the provider package:
// ignore_for_file: public_member_api_docs
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class Counter with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(builder: (_) => Counter()),
],
child: Consumer<Counter>(
builder: (context, counter, _) {
return MaterialApp(
supportedLocales: const [Locale('en')],
localizationsDelegates: [
DefaultMaterialLocalizations.delegate,
DefaultWidgetsLocalizations.delegate,
_ExampleLocalizationsDelegate(counter.count),
],
home: const MyHomePage(),
);
},
),
);
}
}
class ExampleLocalizations {
static ExampleLocalizations of(BuildContext context) =>
Localizations.of<ExampleLocalizations>(context, ExampleLocalizations);
const ExampleLocalizations(this._count);
final int _count;
String get title => 'Tapped $_count times';
}
class _ExampleLocalizationsDelegate
extends LocalizationsDelegate<ExampleLocalizations> {
const _ExampleLocalizationsDelegate(this.count);
final int count;
#override
bool isSupported(Locale locale) => locale.languageCode == 'en';
#override
Future<ExampleLocalizations> load(Locale locale) =>
SynchronousFuture(ExampleLocalizations(count));
#override
bool shouldReload(_ExampleLocalizationsDelegate old) => old.count != count;
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Title()),
body: const Center(child: CounterLabel()),
floatingActionButton: const IncrementCounterButton(),
);
}
}
class IncrementCounterButton extends StatelessWidget {
const IncrementCounterButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: Provider.of<Counter>(context).increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
);
}
}
class CounterLabel extends StatelessWidget {
const CounterLabel({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final counter = Provider.of<Counter>(context);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
);
}
}
class Title extends StatelessWidget {
const Title({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(ExampleLocalizations.of(context).title);
}
}
When the user presses the FloatingRadioButton within IncrementCounterButton, build() is called on CounterLabel and IncrementCounterButton.
They both depend on an inherited widget, which is updated.
How does flutter discover this dependency?
I assume that the BuildContext is modified by the call to Provider.of<>().
Is this why we add the IncrementCounterButton, which has no functionality on its own?
Just to move the call to Provider.of<>() outside of its bigger parent widget, which would be more expensive to rebuild?

The binding widget an InheritedWidget and its consumers is created through BuildContext.
Consider the following InheritedWidget:
class Foo extends InheritedWidget {}
Then the descendants of Foo can subscribe to it by calling:
BuildContext context
context.inheritFromWidgetOfExactType(Foo);
It's worth noting that a widget can obtain the InheritedWidget without subscribing to it, by instead doing:
BuildContext context
context.ancestorInheritedElementForWidgetOfExactType(Foo);
This call is usually performed internally by the .of(context) pattern.
In the case of provider, that subscription is done by calling Provider.of<T>(context).
provider also exposes an optional argument to purposefully not subscribe to the inherited widget:
T value = Provider.of<T>(context, listen: false);

Related

How does the "event" parameter contain data? What should I do if I want to create my "onHover"?

I have a simple flutter application. It's ok, but I'm trying to understand how onHover: (event){...} works, why "event" contains data? How can I make my own widget have function parameters like that?
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
double dx = 0, dy = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Title',
home: Scaffold(
body: MouseRegion(
onHover: (event) {
setState(() {
dx = event.localPosition.dx;
dy = event.localPosition.dy;
});
},
child: Center(
child: Text('$dx'),
),
),
),
);
}
}
To create your own onChange, or the like we can use ValueChanged.
For example, taking a look at the code for a TextButton() we see:
const TextButton({
Key? key,
required VoidCallback? onPressed,
VoidCallback? onLongPress,
ValueChanged<bool>? onHover,
the onHover uses a ValueChanged.
You can implement your own valueChanged using this example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Buttons(
onHover: (value) {
// Do something
print(value);
},
),
),
),
);
}
}
class Buttons extends StatelessWidget {
final ValueChanged<String> onHover;
Buttons({Key? key, required this.onHover}) : super(key: key);
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: () {
onHover('Pressed');
},
child: Text("Click me")),
Text('hi')
],
);
}
}
So this how we pass the data from the widget which is at the bottom of the widget tree.
It's more related to passing the value from bottom to top using callback functions.
Below is the simple example to demonstrate this data sharing.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _parentData = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
"Parent State Value: " + _parentData.toString(),
),
ChildWidgetExample(
callbackFn: (data) {
setState(() {
_parentData = data;
});
},
)
],
);
}
}
class ChildWidgetExample extends StatefulWidget {
final Function(int) callbackFn;
const ChildWidgetExample({
Key? key,
required this.callbackFn,
}) : super(key: key);
#override
State<ChildWidgetExample> createState() => _ChildWidgetExampleState();
}
class _ChildWidgetExampleState extends State<ChildWidgetExample> {
int data = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(
data.toString(),
),
const SizedBox(
height: 30,
),
ElevatedButton(
onPressed: () {
setState(() {
data++;
});
widget.callbackFn(data);
},
child: const Text("Press"),
)
],
);
}
}
In Flutter you can declare Functions with parameters.
void Function(String foo) myFunction;
So you declare in as a variable in your widget component.
MyWidget({required this.myFunction});
Then when you have to call this component you can write :
...
child : MyWidget(myFunction: (String foo) {},),

Flutter - The const widget still rebuild when its parent rebuild

I have a question about when and how a const widget will rebuild.
For example, I have a demo project like this:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
const child = ChildWidget();
return OrientationBuilder(
builder: (context, orientation) {
debugPrint('$orientation');
final isPortrait = orientation == Orientation.portrait;
return Container(
alignment: Alignment.topCenter,
child: isPortrait
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
SizedBox(height: 200, child: child),
Text('Bellow text'),
])
: child,
);
},
);
}
}
class ChildWidget extends StatefulWidget {
const ChildWidget({Key? key}) : super(key: key);
#override
State<ChildWidget> createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
_counter += 1;
});
},
child: Container(
color: Colors.blue, child: Center(child: Text('$_counter'))),
);
}
}
As you see, I have a const ChildWidget.
const child = ChildWidget();
When I rotate the device, it will trigger the builder function of the OrientationBuilder and return a new Container. But my question is why the child widget is rebuilt again while it is a const.
The reason why I want the child widget is not rebuilt is that I don't want the counter Text to reset to 0 each time I rotate the device.
Please advice.
Thanks a lot.
While the variable and object assigned are constant, the framework will still call the build method on the child widgets. So the actual ChildWidget class is not recreated, but the build will be called.
This is not really a problem. Flutter is really optimized for rebuilding Widgets. If the data has not changed, the actual cost of rebuilding is negligible.

Why state change error occurs on flutter_riverpod during initialization

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final dataProvider = StateNotifierProvider<DataNotifier, List<int>>((ref) {
return DataNotifier();
});
class DataNotifier extends StateNotifier<List<int>> {
DataNotifier() : super([]);
Future<void> getData() async {
state = [];
await Future.delayed(const Duration(seconds: 2));
state = [1, 2];
}
}
void main() => runApp(ProviderScope(child: App()));
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => SecondPage()),
);
},
child: const Text('Next page'),
),
),
),
);
}
}
class SecondPage extends ConsumerStatefulWidget {
const SecondPage({Key? key}) : super(key: key);
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends ConsumerState<SecondPage> {
#override
void initState() {
super.initState();
ref.read(dataProvider.notifier).getData();
}
#override
Widget build(BuildContext context) {
final numbers = ref.watch(dataProvider);
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
itemBuilder: (_, index) {
return Text('data: $index');
},
itemCount: numbers.length,
),
);
}
}
I am new to riverpod and I noticed this error while changing state.
In the above code when I tap the "next page" button at the fresh start for the first time it works as expected but when I go back and again tap the "next page" button, an error shown below is thrown:
StateNotifierListenerError (At least listener of the StateNotifier Instance of 'DataNotifier' threw an exception
when the notifier tried to update its state.
Does anyone know why this occurs and how can I prevent it.
You can solve the issue using autoDispose
final dataProvider = StateNotifierProvider.autoDispose<DataNotifier, List<int>>(
(ref) => DataNotifier(),
);
For Future I prefer using FutureProvider.
More about riverpod

How to setState widget by other widget Flutter ,simplecode below

right widget has gesterdetector that adds a String ("ZzZ") to List;
left widget shows all String there in String list by List view Buildder,
right widget adds "ZzZ" to list after pressing the button successfully but it dosent sets ui state...
in android studio after hot reload it shows all added "ZzZ"
import 'package:flutter/material.dart';
List<String> ListOfZzZ=[];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(children: [
Expanded(child:RightSidewidget()),
Expanded(child:LeftSidewidget())
],
)),
);
}
}
class RightSidewidget extends StatefulWidget {
#override
_RightSidewidgetState createState() => _RightSidewidgetState();
}
class _RightSidewidgetState extends State<RightSidewidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(child:Text("add new ZzZ"),),
**onTap: (){
setState(() {
ListOfZzZ.add("ZzZ");
});},);**
}
}
class LeftSidewidget extends StatefulWidget {
#override
_LeftSidewidgetState createState() => _LeftSidewidgetState();
}
class _LeftSidewidgetState extends State<LeftSidewidget> {
#override
Widget build(BuildContext context) {
return Container(child:
ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context,index)=>Text(ListOfZzZ[index])),);
}
}
check the Provider package it can help you achieve what you want, ere is a really good tutorial by the flutter devs showing how to use manage the state of your app and notify widgets of the changes other widgets have.
setState rebuild in very specyfic way. you can read about this in here:
https://api.flutter.dev/flutter/widgets/State/setState.html
in simple world setState call the nearest build (I think this is not full true, but this intuitions works for me)
In your code when you tap right widget and call setState only rightwidget will be rebuild.
So this is the easy solutions:
Make left and right widget statless.
In homescreen in row add gestureDetector(or textButton like in my example) and here call setState. When you do that, all homeSreen will be rebuild so left and right widget too. and your list will be actual. Here is example:
List<String> ListOfZzZ = [];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(
children: [
Expanded(
child: TextButton(
onPressed: () => setState(() {
ListOfZzZ.add("ZzZ");
}),
child: RightSidewidget())),
Expanded(child: LeftSideWidget())
],
)),
);
}
}
class RightSidewidget extends StatelessWidget {
const RightSidewidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.amber[50],
child: Text("add new ZzZ"),
);
}
}
class LeftSideWidget extends StatelessWidget {
const LeftSideWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context, index) => Text(ListOfZzZ[index])),
);
}
}
The hard way, but more elegant and better is to use some state manager like bloc. Here is official site: https://bloclibrary.dev/#/gettingstarted
there is a lot of tutorials and explanations. But this is not solutions for 5 minutes.
Edit: I make some solution with BLoC. I hope this help. I use flutter_bloc and equatable packages in version 7.0.1
void main() {
EquatableConfig.stringify = kDebugMode;
Bloc.observer = SimpleBlocObserver();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('myList'),
),
body: BlocProvider(
create: (context) => MylistBloc()..add(AddToList('Start')),
child: Row(
children: [
Expanded(flex: 1, child: buttonsPanel()),
Expanded(flex: 1, child: ListOfZzZ()),
],
),
),
),
);
}
}
class ListOfZzZ extends StatefulWidget {
const ListOfZzZ({Key? key}) : super(key: key);
#override
_ListOfZzZState createState() => _ListOfZzZState();
}
class _ListOfZzZState extends State<ListOfZzZ> {
late MylistBloc _mylistBloc;
#override
Widget build(BuildContext context) {
return BlocBuilder<MylistBloc, MylistState>(
//builder: (context, state) {return ListView.builder(itemBuilder: (BuildContext context,int index){return ListTile(title: state.positions[index];)},);},
builder: (context, state) {
if (state.positions.isEmpty) {
return const Center(child: Text('no posts'));
} else {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(state.positions[index]));
},
itemCount: state.positions.length,
);
}
},
);
}
}
class buttonsPanel extends StatefulWidget {
const buttonsPanel({Key? key}) : super(key: key);
#override
_buttonsPanelState createState() => _buttonsPanelState();
}
class _buttonsPanelState extends State<buttonsPanel> {
late MylistBloc _mylistBloc;
#override
void initState() {
super.initState();
_mylistBloc = context.read<MylistBloc>();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Spam'))},
child: Text('Spam')),
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Ham'))},
child: Text('Ham')),
],
);
}
class SimpleBlocObserver extends BlocObserver {
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print(error);
super.onError(bloc, error, stackTrace);
}
}
class MylistState extends Equatable {
final List<String> positions;
final int lenght;
const MylistState({this.positions = const <String>[], this.lenght = 0});
#override
List<Object> get props => [positions];
#override
String toString() => 'Lenght: {$lenght} Positions: {$positions}';
#override
MylistState copyWith(List<String>? positions) {
return MylistState(positions: positions ?? this.positions);
}
}
abstract class MylistEvent extends Equatable {
const MylistEvent();
#override
List<Object> get props => [];
}
class AddToList extends MylistEvent {
final String posToAdd;
#override
AddToList(this.posToAdd);
}
class MylistBloc extends Bloc<MylistEvent, MylistState> {
MylistBloc() : super(MylistState(positions: const <String>[]));
#override
Stream<MylistState> mapEventToState(
MylistEvent event,
) async* {
if (event is AddToList) {
yield await _mapListToState(state, event.posToAdd);
}
}
Future<MylistState> _mapListToState(
MylistState state, String posToAdd) async {
List<String> positions = [];
positions.addAll(state.positions);
positions.add(posToAdd);
return MylistState(positions: positions, lenght: positions.length);
}
}
}

Riverpod | How many Providers are really needed to watch only a single state of a class

I followed this excellent Riverpod tutorial. In the last steps the author uses the following code:
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
and
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
I tried using _buttonState and _timeLeftProvider and, from what I see, the app works correctly. So, my questions are:
What need is there to create and use buttonProvider and timeLeftProvider?
How many Providers are really needed?
Thank you very much!
2020-10-26 UPDATE (main.dart code and output image)
My main.dart code is:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
final buttonProvider = Provider<ButtonState>((ref) {
return ref.watch(_buttonState);
});
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
final timeLeftProvider = Provider<String>((ref) {
return ref.watch(_timeLeftProvider);
});
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final timeLeft = useProvider(timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(buttonProvider);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
If I click on the ‘Play’ button and then let the 10 seconds pass, in the end I get the same result in the 2 cases:
2020-10-27 UPDATE (main.dart code without using buttonProvider and timeLeftProvider)
This is the output even if buttonProvider and timeLeftProvider are not used, like in the following main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_timer_app/timer.dart';
final timerProvider = StateNotifierProvider<TimerNotifier>(
(ref) => TimerNotifier(),
);
final _buttonState = Provider<ButtonState>((ref) {
return ref.watch(timerProvider.state).buttonState;
});
// final buttonProvider = Provider<ButtonState>((ref) {
// return ref.watch(_buttonState);
// });
final _timeLeftProvider = Provider<String>((ref) {
return ref.watch(timerProvider.state).timeLeft;
});
// final timeLeftProvider = Provider<String>((ref) {
// return ref.watch(_timeLeftProvider);
// });
void main() {
runApp(
const ProviderScope(child: MyApp()),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('building MyHomePage');
return Scaffold(
appBar: AppBar(title: Text('My Timer App')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TimerTextWidget(),
SizedBox(height: 20),
ButtonsContainer(),
],
),
),
);
}
}
class TimerTextWidget extends HookWidget {
const TimerTextWidget({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final timeLeft = useProvider(_timeLeftProvider);
print('building TimerTextWidget $timeLeft');
return Text(
timeLeft,
style: Theme.of(context).textTheme.headline2,
);
}
}
class ButtonsContainer extends HookWidget {
const ButtonsContainer({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ButtonsContainer');
final state = useProvider(_buttonState);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (state == ButtonState.initial) ...[
StartButton(),
],
if (state == ButtonState.started) ...[
PauseButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.paused) ...[
StartButton(),
SizedBox(width: 20),
ResetButton(),
],
if (state == ButtonState.finished) ...[
ResetButton(),
],
],
);
}
}
class StartButton extends StatelessWidget {
const StartButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building StartButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).start,
child: Icon(Icons.play_arrow),
);
}
}
class PauseButton extends StatelessWidget {
const PauseButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building PauseButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).pause,
child: Icon(Icons.pause),
);
}
}
class ResetButton extends StatelessWidget {
const ResetButton({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('building ResetButton');
return FloatingActionButton(
onPressed: context.read(timerProvider).reset,
child: Icon(Icons.replay),
);
}
}
What am I doing wrong?
Those providers are used to prevent unnecessary rebuilds but aren't fundamentally necessary. Only create providers you need - especially as these providers will never be disposed of in the app lifecycle, they are just wasted space. However, preventing unnecessary rebuilds should be the top priority.
In the linked article, the author is utilizing a workaround recommended by the package author to prevent rebuilds when listening to a specific attribute of a StateNotifier. So, for now, that is the most efficient way to accomplish the task. I will try to update this answer if new functionality is introduced to solve it.
I would refer to the package creator's examples for more context.
Here's a quick example of why you might use multiple providers to cache responses from an external API:
class ExampleApiRepository {
ExampleApiRepository(this._read);
static final provider = Provider((ref) => ExampleApiRepository(ref.read));
final Reader _read;
Future<Example> search(String query) async {
final response = await _call('api/example/$query');
return Example.fromJson(response.data);
}
}
final searchExample = FutureProvider.family<Example, String>((ref, query) async {
return ref.watch(ExampleApiRepository.provider).search(query);
});
In this example, if the same query is passed to the searchExample provider, it will return the previously fetched result. Could this be achieved without multiple providers? Yes - and for most cases this will hold true. Creating a provider is about convenience and efficiency. So don't be afraid to use many providers, but don't create them for the sake of creating them.
That said, the article you linked is informative and appreciated.