Flutter provider can't re-render immediately - flutter

Here is my source code: https://github.com/liou-jia-hao/flutter_demo_app/tree/Cannot-refresh
I create a file which contains my counter model called "counter.dart", here is code:
import 'package:flutter/foundation.dart';
import 'package:nanoid/nanoid.dart';
class Counter {
String id;
int count;
void increment() {
count++;
}
void decrement() {
count--;
}
Counter(this.id, this.count);
}
class CountersModel with ChangeNotifier {
Map<String, Counter> countersMap = {};
void createCounter() {
var id = nanoid();
countersMap[id] = Counter(id, 0);
notifyListeners();
}
}
And here is my main.dart code:
// ignore_for_file: public_member_api_docs, lines_longer_than_80_chars
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/counter.dart';
/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CountersModel()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: ThemeMode.dark,
theme: ThemeData(brightness: Brightness.dark),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var countersSet = context.select<CountersModel, Set<Counter>>(
(model) => model.countersMap.values.toSet());
var countersModel = context.read<CountersModel>();
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: countersSet.map((counter) => BlueButton(counter)).toList(),
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),
/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: countersModel.createCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class BlueButton extends StatelessWidget {
const BlueButton(this.counter, {Key? key}) : super(key: key);
final Counter counter;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: counter.increment,
onLongPress: counter.decrement,
child: Column(
children: [Text(counter.id), Text('${counter.count}')],
),
);
}
}
I expected the number on the BlueButton can increase immediately.
But the number on the BlueButton can't increase immediately.

I believe that your counter needs context in order for the button to change.
Take a look at this answer.

Related

Flutter - How to call an API every n minutes?

I need to call an API every n minutes. The data should be available across all screens. How can I implement this at app level. I am not using any state management tools.
void main() {
periodicSub = Stream.periodic(const Duration(seconds: 10))
.listen((_) {
///fetch data
someFuture =
Future<List<someObject>>.delayed(
const Duration(milliseconds: 500), () async {
return someFunction();
});
});
someFuntions returns a list. I want a certain FutureBuilder on HomePage to execute whenever the list is updated.
Here is an example using "https://pub.dev/packages/provider"
First create a Notifier:
import 'dart:async';
import 'package:flutter/material.dart';
class CustomNotifier with ChangeNotifier {
int counter = 0;
CustomNotifier() {
Stream.periodic(const Duration(seconds: 10)).listen((_) {
///fetch data
Future<List<dynamic>>.delayed(const Duration(milliseconds: 500),
() async {
return someFunction();
});
});
}
someFunction() {
counter++;
notifyListeners();
}
}
Then you could use it like:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'notifier.dart';
void main() {
final customNotifier = CustomNotifier();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => customNotifier,
),
//You could add more providers
],
builder: (context, _) {
return const MyApp();
},
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
var customNotifier = Provider.of<CustomNotifier>(
context,
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'someFunction runs this many times:',
),
Text(
'${customNotifier.counter}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}

Can i passing nothing in BlocBuilder with if else statement

I don't want to pass data to text widget if the counterValue number is less than 0. This the code:
main.dart :
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_bloc_concepts/cubit/cubit/counter_cubit.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<CounterCubit>(
create: (context) => CounterCubit(),
child: const MaterialApp(
title: 'flutter_bloc Demo',
debugShowCheckedModeBanner: false,
home: HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Flutter BLoC Concepts DEMO")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text("You have pushed the button this many times:"),
// Bloc Builder
BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
if (state.counterValue < 0) {
return NOTHING;
} else {
return Text(
state.counterValue.toString(),
style: Theme.of(context).textTheme.headline4,
);
}
},
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FloatingActionButton(
onPressed: () {
BlocProvider.of<CounterCubit>(context).decrement();
},
tooltip: "Decrement",
child: const Icon(Icons.remove),
),
FloatingActionButton(
onPressed: () {
BlocProvider.of<CounterCubit>(context).increment();
},
tooltip: "Increment",
child: const Icon(Icons.add),
),
],
),
],
),
),
);
}
}
counter_cubit.dart :
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
part 'counter_state.dart';
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState(counterValue: 0));
void increment() => emit(CounterState(counterValue: state.counterValue + 1));
void decrement() => emit(CounterState(counterValue: state.counterValue - 1));
}
counter_state.dart :
part of 'counter_cubit.dart';
class CounterState {
int counterValue;
CounterState({
required this.counterValue,
});
}
Can i pass nothing to text widget when counterValue number is less than 0. I don't want to show negative numbers, so if I press the decrement button when counterValue = 0 the number displayed is not negative -1,-2... / I want stay at 0. Can i do that
adding if else statement in counter_cubit.dart :
void decrement() {
if (state.counterValue <= 0) {
state.counterValue = 0;
} else {
emit(CounterState(counterValue: state.counterValue - 1));
}
}

Flutter - Best way to aggregate data from child widgets in an IndexedStack

I have an IndexedStack in a Scaffold that I use to manage my registration. The Registration widget itself is Stateful, but the widgets that compose it are Stateless. The parent widget looks like this:
class Registration extends StatefulWidget {
#override
_RegistrationState createState() => _RegistrationState();
}
class _RegistrationState extends State<Registration> {
int _index = 0;
void _nextPage() {
setState(() {
_index++;
});
}
void _prevPage() {
setState(() {
_index--;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
leading: new IconButton(
icon: new Icon(Icons.arrow_back,
color: Theme.of(context).primaryColor),
onPressed: () {
if (_index == 0) {
Navigator.pop(context);
} else {
_prevPage();
}
}),
elevation: 0.0,
),
body: IndexedStack(
children: <Widget>[
RegistrationPhone(_nextPage),
RegistrationName(_nextPage),
RegistrationBirthday(_nextPage),],
index: _index,
),
);
}
}
What is the best way to take data from these child widgets?
Should I pass in a callback function and hold the data in the parent? Should I pass the information down the line from widget to widget until it's submitted? I don't know what the practices are for sharing data across multiple screens.
Use Provider
Add Dependency :
dependencies:
provider: ^4.3.3
here is the Example :
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),
/// Extracted as a separate widget for performance optimization.
/// As a separate widget, it will rebuild independently from [MyHomePage].
///
/// This is totally optional (and rarely needed).
/// Similarly, we could also use [Consumer] or [Selector].
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),
/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Count extends StatelessWidget {
const Count({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(
/// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headline4);
}
}

How to invoke a rebuild of a stateless widget?

Context
I have two stateless widgets (pages): HomePage and DetailsPage. Obviously the application starts and launches the HomePage. There is a button the user can press to navigate to the DetailsPage with a Navigator.pop() button to navigate back to the HomePage.
I know when the DetailsPage is done being used with the .whenComplete() method. It is at this point I want to rebuild the HomePage widget.
Code
This is the minimum reproduction of my behavior.
main.dart
import 'package:example/home.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(home: HomePage());
}
}
home.dart
import 'package:example/details.dart';
import 'package:flutter/material.dart';
class HomePage extends StatelessWidget {
static const name = 'Home Page';
const HomePage() : super();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(() => print('Rebuild now.'));
},
),
),
);
}
}
details.dart
import 'package:flutter/material.dart';
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => DetailsPage();
const DetailsPage();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
Question
How can I invoke a rebuild of this stateless widget (HomePage) at the .whenComplete() method callback?
You can force rebuild the widget tree as follows:
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
In your case,
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final RebuildController controller = RebuildController();
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RebuildWrapper(
controller: controller,
child: HomePage(
rebuildController: controller,
),
),
);
}
}
class HomePage extends StatelessWidget {
static const name = 'Home Page';
final RebuildController rebuildController;
const HomePage({Key? key, required this.rebuildController}) : super(key: key);
#override
Widget build(BuildContext context) {
print('Hello there!');
return Scaffold(
body: Center(
child: MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text(name),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: DetailsPage.builder),
).whenComplete(rebuildController.rebuild);
},
),
),
);
}
}
class DetailsPage extends StatelessWidget {
static const name = 'Details Page';
static WidgetBuilder builder = (BuildContext _) => const DetailsPage();
const DetailsPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(name),
MaterialButton(
color: Colors.blue,
textColor: Colors.white,
child: const Text('Go Back'),
onPressed: () => Navigator.pop(context),
),
],
),
),
);
}
}
class RebuildController {
final GlobalKey rebuildKey = GlobalKey();
void rebuild() {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(rebuildKey.currentContext as Element).visitChildren(rebuild);
}
}
class RebuildWrapper extends StatelessWidget {
final RebuildController controller;
final Widget child;
const RebuildWrapper({Key? key, required this.controller, required this.child}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
key: controller.rebuildKey,
child: child,
);
}
But it is unnatural to force rebuild stateless widgets as they are not supposed to be rebuilt. You should use stateful widget or other state management solutions so that your HomePage will only be updated on meaningful state change.
Source - this answer

Flutter navigator.push() object issue

While passing an object from one class to another class by using Navigator.push(), the object does not get modifying even its declared as not final.
Main Screen : Created an object(userBean) and passing to First screen
First Screen : displaying the same object(userBean) values, and passing again the same object(userBean) to second screen.
Second screen : trying to get modify the same object (userBean) in second screen, and printing the same object(userBean) in first screen by using refreshData.then method.
Main.dart
import 'package:flutter/material.dart';
import 'package:flutter_app_poc1/firstSceeen.dart';
import 'package:flutter_app_poc1/secondScreen.dart';
import 'package:flutter_app_poc1/userbean.dart';
void main() => runApp(MyApp());
typedef void refreshCallBack(int index);
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,
),
home: 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> {
UserBean user = new UserBean();
final List<String> hhList = ["General", "edu"];
int _counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new FlatButton(
child: Text("Next Screen"),
onPressed: () {
user.id = 1;
user.name = "Ramesh";
Future<dynamic> refreshData =
Navigator.of(context).push(new MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return new FirstScreen(userbean: user);
},
));
refreshData.then((_) {
});
}),
],
),
),
);
}
}
Firstscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_app_poc1/secondScreen.dart';
import 'package:flutter_app_poc1/userbean.dart';
typedef void refreshCallBack(int index);
class FirstScreen extends StatefulWidget {
UserBean userbean;
FirstScreen({Key key, this.userbean}) : super(key: key);
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
String userName;
final List<String> hhList = ["General", "edu"];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("first"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(widget.userbean.name),
new RaisedButton(onPressed: (){
Future<dynamic> refreshData =
Navigator.of(context).push(new MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return new SecondScreen(userbean: widget.userbean);
},
));
refreshData.then((_) {
print(widget.userbean.name);
});
}),
],
),
),
);
}
}
secondscreen.dart
import 'package:flutter/material.dart';
import 'package:flutter_app_poc1/userbean.dart';
class SecondScreen extends StatefulWidget {
UserBean userbean;
SecondScreen({Key key, this.userbean}) : super(key: key);
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
UserBean bean = UserBean();
#override
Widget build(BuildContext context) {
bean.name = "suresh";
return Scaffold(
appBar: AppBar(
title: Text("Previous Screen"),
),
body: Center(
child: new FlatButton(
child: Text(bean.name),
onPressed: () {
print(bean.name);
widget.userbean = bean;
Navigator.pop(context, true);
}),
));
}
}
#Murali
If you want to follow the same procedure pass object, then follow the below procedure.
From Navigator.pop push again new Object
onPressed: () {
print("TEST second screen :"+bean.name);
/// here modifying with new object.
widget.userbean = bean;
Navigator.pop(context, widget.userbean);
}),
In second screen Get new Object from Feature Method as below
Future<UserBean> refreshData =
Navigator.of(context).push(new MaterialPageRoute<UserBean>(
builder: (BuildContext context) {
return new SecondScreen(userbean: widget.userbean);
},
));
refreshData.then((res) {
print("TEST First screen : ${res.name}");
});
Then Object will change with new values.