Difference Consumer / Provider.of in ChangeNotifierProvider - flutter

I don't really understand the difference between Provider.of() and Consumer.
I have read here that Consumer is like Provider.of with listen: true.
However, in the bellow example, I don't get an error when I use Consumer, but I get one while using Provider.of. I am forced to use listen: false. The below example is the default flutter app with ChangeNotifierProvider implemented.
I will just change the code in floatingActionButton in main.dart to see diffences between Consumer, Provider.of listen: true and Provider.of listen: false
Code of counter.dart
import 'package:flutter/material.dart';
class Counter extends ChangeNotifier {
int value = 0;
void increment() {
value++;
notifyListeners();
}
void decrement() {
value--;
notifyListeners();
}
}
Full code of main.dart, with floatingActionButton using Consumer. It is working
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:unit_test/counter.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ChangeNotifierProvider<Counter>(
create: (context) => Counter(),
child: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Consumer<Counter>(builder: (context, counter, child) {
return Text(
counter.value.toString(),
style: Theme.of(context).textTheme.headline4,
);
}),
],
),
),
floatingActionButton:
Consumer<Counter>(builder: (context, counter, child) {
return FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}),
);
}
}
Code of floatingActionButton using Provider.of, listen: true, not working
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context, listen: true).increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
Error : Tried to listen to a value exposed with provider, from outside of the widget tree...
Code of floatingActionButton using Provider.of, listen: false, working
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<Counter>(context, listen: false).increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
I don't get it. I put listen: false, but it is still listening and rebuilding the widget
Thank you for your help

listen:true needs to put inside a build widget tree. It purpose is to rebuild the whole widget if called (notifyListener).
#override
Widget build(BuildContext context) {
Counter counter = Provider.of<Counter>(context, listen: true);
...
counter.increment(); // Will rebuild entire widget.
...
Consumer is a widget that call listen:true inside it but will only rebuild it's child widget.
Consumer<Counter>(builder: (_, counter, __) {
return Column(
children:[
TextButton(
onPressed: () => counter.increment(), // Will only rebuild this Column
child: Icon(Icons.add),),
Text(counter.value.toString());
}),
listen:false will access Provider without rebuild widget if called.
TextButton(
onPressed: () {
Counter counter = Provider.of<Counter>(context, listen: false);
counter.increment();
// Will change Provider value, but won't rebuild. It will make widget that has
// Consumer as parent to rebuild (or with listen:true to rebuild).
}
child: Icon(Icons.add),
),

Related

Close screen when a value changes in Riverpod

How do I close a screen (call Navigator.pop) when a certain value changes?
I am using riverpod to watch for the values.
Use the WidgetRef.listen to listen to state changes. From the docs:
Listen to a provider and call listener whenever its value changes.
This is useful for showing modals or other imperative logic.
The code is going to be something like this:
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
#override
Widget build(context, ref) {
ref.listen(theProvider, (previous, next) {
if (isTheEnd(next)) {
Navigator.of(context).pop();
}
});
...
Here's a complete example:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends ConsumerWidget {
const MyHomePage({super.key});
#override
Widget build(context, ref) {
ref.listen(counterProvider, (previous, next) {
if (next % 2 == 0) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text('Value is even: $next'),
),
);
}
});
final counter = ref.watch(counterProvider);
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => ref.read(counterProvider.notifier).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
final counterProvider = StateNotifierProvider<Counter, int>((ref) {
return Counter();
});
class Counter extends StateNotifier<int> {
Counter() : super(0);
void increment() => state++;
}
You need to create some kind of Function that handle that. The function required a build context to get the Navigator and you could call the Navigator.of(context).pop or Navigator.pop(context) when you want.
onChangeValue(BuildContext context, dynamic value){
if(value!=null){
Navigator.of(context).pop();
}
}

How to Passing Data from Navigator Pop to Previous Page Where The Data is Used in The Widget Inside the ListView.builder

As stated in the title. How to return data to the previous page where the data is used to list widgets.
I have read this article Flutter Back button with return data or other similar articles. The code works perfectly. But there is a problem if I want to use the data returned to the widget that is in the list.\
Note that I only want to update one ListWidget, I don't want to refresh the state of the entire HomePage like the solution in this article Flutter: Refresh on Navigator pop or go back.
Here is a simple code sample to represent the problem I'm facing.
(check on ListWidget Class and SecondPage Class below)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
HomePage class
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: ListView.builder(
itemCount: 4,
itemBuilder: (_, index){
return ListWidget(number: index+1);
},
)
),
);
}
}
ListWidget Class
class ListWidget extends StatelessWidget{
ListWidget({#required this.number});
final int? number;
String? statusOpen;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
},
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(20),
color: Colors.amber,
child: Text(statusOpen != null ? '$number $statusOpen' : '$number Unopened'),
//
// I want to change the text here to 'has Opened' when the user returns from SecondPage
//
),
);
}
}
SecondPage Class
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context, 'has Opened');
// return 'has Opened' to await statusOpen variable
},
child: Text('Go Back'),
),
),
);
}
}
is there any solution to handle this?
If you make your listWidget a stateful widget, then you can get the solution where you just need to call setState when you return to your previous screen. And in this way you will be only changing your single list element and not the full screen.
sample code:
changing this line- class ListWidget extends StatefulWidget
and adding these lines -
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
setState(() {
});
},
If you used the data in your listview just call setstate after Navigator.pop like below code
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
).then((value) async {
setState(() {});
});
},

I am using BLOC to make a simple counter app , but my counter is not updating when I am incrementing / decrementing it? I used BlocBuilder to build UI

I have wrapped MaterialApp under the BlocProvider and also I have used BlocBuilder for building the UI ( counter text )
I have two floating action buttons each of which call Incrementevent() and Decrementevent() respectively but still, the UI doesn't change.
please suggest a solution.
My main.dart file
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _counterBloc = CounterBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${state.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
},
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => _counterBloc.add(Incrementevent()),
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
SizedBox(width: 10),
FloatingActionButton(
onPressed: () => _counterBloc.add(Decrementevent()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
],
),
);
}
#override
void dispose() {
// TODO: implement dispose
_counterBloc.close();
super.dispose();
}
}
My counter_event file
part of 'counter_bloc.dart';
#immutable
abstract class CounterEvent {}
class Incrementevent extends CounterEvent {}
class Decrementevent extends CounterEvent {}
My counter_state file
part of 'counter_bloc.dart';
#immutable
abstract class CounterState {
final int counter;
const CounterState(this.counter);
}
class CounterInitial extends CounterState {
CounterInitial(int counter) : super(counter);
}
My counter_bloc file
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'counter_event.dart';
part 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitial(0));
#override
Stream<CounterState> mapEventToState(
CounterEvent event,
) async* {
if (event is Incrementevent) {
yield CounterInitial(state.counter + 1);
} else if (event is Decrementevent) {
yield CounterInitial(state.counter - 1);
}
}
}
TL;DR You are creating two instances of the same BLoC, hence by triggering events on one BLoC, the other does not get updated.
In the MyApp widget, you create a BLoC instance and provide it to the widget tree using the BlocProvider - everything is perfect here.
However, in the _MyHomePageState, you are creating another instance of the same CounterBloc and using it to add events. It means, that you are operating on a different BLoC than you provided before.
To resolve this, you should get the injected BLoC instance and operate on it. Since the BLoC library depends on provider, you could resolve the injected BLoC like this:
#override
Widget build(BuildContext context) {
final counterBloc = context.watch<CounterBloc>();
...
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => counterBloc.add(Incrementevent()),
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
SizedBox(width: 10),
FloatingActionButton(
onPressed: () => counterBloc.add(Decrementevent()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
],
),
...
}
Or this:
#override
Widget build(BuildContext context) {
...
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Incrementevent()),
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
SizedBox(width: 10),
FloatingActionButton(
onPressed: () => context.read<CounterBloc>().add(Decrementevent()),
tooltip: 'Increment',
child: Icon(Icons.add),
),
],
),
...
}

FLUTTER/DART - TEXT not displaying to home.dart file within a FloatingActionButton

Having a problem with getting text to display in my home.dart file when it's entered in the FloatingActionButton.
Below is the code sample. Any suggestions where I am getting it wrong. I believe that the 'String value;' line must be within the same MaterialButton function, though not sure how to do it without ruining it further.
}`
I'm using a simple app here to demonstrade the behavior. You can test by copy and running this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SomeScreen(),
);
}
}
class SomeScreen extends StatefulWidget {
#override
_SomeScreenState createState() => _SomeScreenState();
}
class _SomeScreenState extends State<SomeScreen> {
String value = '';
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _createDialog(context);
setState(() {});
},
child: Icon(Icons.add),
backgroundColor: Colors.red,
),
body: Container(
color: Colors.blue,
child: Center(
child: Text(value),
),
),
);
}
_createDialog(context) async {
await showDialog(
context: context,
builder: (BuildContext dialogContext) {
return AlertDialog(
title: Text('title'),
content: TextField(
onChanged: (text) {
value = text;
},
),
actions: <Widget>[
FlatButton(
child: Text('buttonText'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
}

Showing a dialog that is in another file in flutter app

I'm trying to show a dialog box that is in another file in a StatefullWidget but when I call its function nothing is happening.
The reason I want to do this is because there is too much nesting of code in my code so I want to keep things simple and clean.
Below is the dialog.dart file.
import 'package:flutter/material.dart';
class PersonDetailsDialog extends StatefulWidget {
PersonDetailsDialog({Key key}) : super(key: key);
#override
_PersonDetailsDialogState createState() {
return _PersonDetailsDialogState();
}
}
class _PersonDetailsDialogState extends State<PersonDetailsDialog> {
#override
Widget build(BuildContext context) {
Future<void> _neverSatisfied() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}
Below is the main.dart file.
mport 'package:flutter/material.dart';
import 'package:practical_0/homepage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue
),
home: Homepage(),
);
}
}
Below is homepage.dart file where I'm trying to show the dialog when the user clicks RaisedButton but nothing happens.
import 'package:flutter/material.dart';
class Homepage extends StatelessWidget {
final double heightFactor = 600/896;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: RaisedButton(
onPressed: PersonDetailsDialog(), // show dialog
),
),
);
}
}
You have to use ShowDialog Where You want to show dialog.
I hope that following example clear your idea.
class Delete extends StatefulWidget {
#override
_DeleteState createState() => _DeleteState();
}
class _DeleteState extends State<Delete> {
BuildContext parent, child;
#override
Widget build(BuildContext context) => Scaffold(
body: Container(
child: Center(
child: RaisedButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
child: PersonDetailsDialog());
}, // show dialog
),
),
),
);
}
class PersonDetailsDialog extends StatefulWidget {
PersonDetailsDialog({Key key}) : super(key: key);
#override
_PersonDetailsDialogState createState() {
return _PersonDetailsDialogState();
}
}
class _PersonDetailsDialogState extends State<PersonDetailsDialog> {
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Rewind and remember'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text('You will never be satisfied.'),
Text('You\’re like me. I’m never satisfied.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Regret'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
}
Here is an example:
Show dialog is an async function
child: RaisedButton(
onPressed: () async{
final result = await showDialog(
context: context,
builder: (_) => AlertWidget(),
);
return result;
},