Why "StateNotifierProvider + Consumer" is not rebuilding the widget? - flutter

This is de main.dart file of a flutter app using riverpod_flutter package.
Starting at 3, the counter is increasing its value by 2 on each click, but the screen doesn't refresh (it shows 3 while the console prints: 5, 7, 9, etc...).
This is the code for "StateNotifierProvider + Consumer", but I get the same result when using ConsumerWidget instead of Consumer.
Any suggestion?
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
class Counter extends StateNotifier<int> {
Counter() : super(3);
String toString() => state.toString();
void increment() => state++;
void decrement() => state--;
}
void main() {
runApp(ProviderScope(
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RiverPod Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('RiverPod example'),
),
body: Container(
child: Center(
child: Consumer(builder: (context, watch, child) {
Counter data = context.read(counterProvider.notifier);
Counter data2 = watch(counterProvider.notifier);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(data.toString()),
Text(data2.toString()),
SizedBox(
height: 10,
),
FloatingActionButton(
onPressed: () {
context.read(counterProvider.notifier).increment();
data2.increment();
print(data.toString());
},
child: const Icon(Icons.add),
)
],
);
}),
),
),
);
}
}
Thank you.

With watch(counterProvider.notifier), you are not actually watching the state, but you are watching the notifier itself (that is why you can call increment). You should not use watch to get the notifier, but only context.read (as you did with the data property).
To rebuild when the state changes, you have to watch the actual state: watch(counterProvider);. This will return the integer state, which you can then use to display the current value.
You also should not get the state directly from the notifier (your toString) method. The StateNotifier should be used to manage te state, but the actual state itself should be used to render the Widgets.

Change 1:
Instead of this:
Counter data = context.read(counterProvider.notifier);
Counter data2 = watch(counterProvider.notifier);
Use this:
final int data = context.read(counterProvider);
final int data2 = watch(counterProvider);
Change 2:
Instead of this:
context.read(counterProvider.notifier).increment();
data2.increment();
Use this:
context.read(counterProvider.notifier).increment();
watch(counterProvider.notifier).increment();
Change 3 (optional, no needed to make it work):
Comment this line:
//String toString() => state.toString();
Change 4 (optional, no needed to make it work):
Instead of this:
final counterProvider = StateNotifierProvider<Counter, int>((ref) => Counter());
Use this:
final counterProvider = StateNotifierProvider((ref) => Counter());
But if you do, you need to change also this:
final int data = context.read(counterProvider);
final int data2 = watch(counterProvider);
For this:
final int data = (context.read(counterProvider) as int);
final int data2 = (watch(counterProvider) as int);
Or this:
final data = context.read(counterProvider);
final data2 = watch(counterProvider);
This is the final code (one version):
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
final counterProvider = StateNotifierProvider((ref) => Counter());
class Counter extends StateNotifier<int> {
Counter() : super(3);
//String toString() => state.toString();
void increment() => state++;
void decrement() => state--;
}
void main() {
runApp(ProviderScope(
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'RiverPod Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Home Page'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('RiverPod example'),
),
body: Container(
child: Center(
child: Consumer(builder: (context, watch, child) {
//Counter data = context.read(counterProvider.notifier);
//Counter data2 = watch(counterProvider.notifier);
final int data = (context.read(counterProvider) as int);
final int data2 = (watch(counterProvider) as int);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(data.toString()),
Text(data2.toString()),
SizedBox(
height: 10,
),
FloatingActionButton(
onPressed: () {
context.read(counterProvider.notifier).increment();
watch(counterProvider.notifier).increment();
print(data.toString());
},
child: const Icon(Icons.add),
)
],
);
}),
),
),
);
}
}

Related

Flutter: scoped model access in StatefulWidget

I have scoped model lib/scoped_models/main.dart:
import 'package:scoped_model/scoped_model.dart';
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And very simple app lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: MainModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
final MainModel _model = MainModel();
void initState() {
super.initState();
// _model.incrementCount(); // <-- doesn't work !!!
}
void _incrementCounter() {
setState(() {
// _model.incrementCount(); // <-- doesn't work !!!
});
}
#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:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
model.incrementCount(); // <-- only this works !!!
// _incrementCounter(); // <-- doesn't work !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
The problem that I can't access MainModel outside of ScopedModelDescendant widget.
How to call MainModel methods at the beginning of _MyHomePageState class?
I believe it is possible because I don't want to keep all logic just in MainModel class and call every method in ScopedModelDescendant widget because it would be very inconvenient if there were many nested widgets.
So, how to get access to scoped model in StatefulWidget?
Use Scoped Model as provider
add ScopedModel just before the widget which use it (MyHomePage)
use ScopedModel.of<MainModel>(context) to control the model
use ScopedModelDescendant<MainModel> to listen the model
The advantage of using this:
You can access the same model in the descendants and share data easily
rebuild widget as small as possible (only ScopedModelDescendant part will be rebuilt)
code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ScopedModel<MainModel>(
model: MainModel(),
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> {
void initState() {
super.initState();
}
void _incrementCounter() {
ScopedModel.of<MainModel>(context).incrementCount();
}
#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:'),
ScopedModelDescendant<MainModel>(
builder: (context,child, model){
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Put MainModel as a Singleton
As your solution, you create MainModel once and make it final. This can be more simple like below:
MainModel
final MainModel mainModel = MainModel();
class MainModel{
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
}
void setCount(int value) {
_count = value;
}
}
MyHomePage
MainModel even no need to extend Model or use notifyListeners becaue the widget use setState to rebuild
code:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount();
});
}
#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:',
),
Text(
'${mainModel.count}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
After watching into my code for a while I realized how stupid simple it was to fix.
So, obviously there should be just one instance of MainModel() for all widgets and files of the project and for convenience it should be placed in scoped model file lib/scoped_models/main.dart like this:
import 'package:scoped_model/scoped_model.dart';
final MainModel mainModel = MainModel(); // <-- create instance once for all files which require scoped model import
class MainModel extends Model {
int _count = 0;
int get count {
return _count;
}
void incrementCount() {
_count += 1;
notifyListeners();
}
void setCount(int value) {
_count = value;
notifyListeners();
}
And then you can use mainModel instance anywhere you import the model import 'package:<app_name>/scoped_models/main.dart';
So that, this code will be valid lib/main.dart:
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:scoped_m_test/scoped_models/main.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ScopedModel<MainModel>(
model: mainModel, // <-- instance of model from 'lib/<app_name>/scoped_models/main.dart'
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
mainModel.incrementCount(); // <-- now it works !!!
});
}
#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:',
),
ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return Text(
'${model.count}',
style: Theme.of(context).textTheme.headline4,
);
}
)
],
),
),
floatingActionButton: ScopedModelDescendant<MainModel>(
builder: (BuildContext context, Widget child, MainModel model) {
return FloatingActionButton(
onPressed: () {
// model.incrementCount(); // <-- works !!!
_incrementCounter(); // <-- now it's working too !!!
},
tooltip: 'Increment',
child: Icon(Icons.add),
);
}
)
);
}
}
Despite that fact that is seems reasonable, it can be overwhelming as well for the first time due to lack of examples.

Flutter: Detect rebuild of any widget which is not visible on screen but is in the widget tree

Summary:
As showing a page/route using the Navigator, a new branch is created from the nearest MaterialApp parent. Meaning both pages (Main & New) will be in memory and will rebuild if they are listening to the same ChangeNotifier.
I am having trouble finding out which widget is on-screen currently visible to the user.
I need this to handle a scenario to skip performing asynchronous or long processes with some side effects, from a widget that might be in the widget tree but currently not visible.
Note: The sample code given here represents the basic architecture of the app I am currently working on, but reproduces the exact problem.
I am having this problem with a very different and complex widget tree that I have in my app, executing the doLongProcess() from a widget that is not visible on the screen. Also doLongProcess() changes some common property in my app which causes an issue, as any background widget can modify the details which are visible on the other widget.
I am looking for a solution to this issue, if there's any other way to achieve the goal except finding which widget is on the screen then please let me know that as well.
My final goal is to allow the long process to be executed from only the visible widget(s).
Please run the app once, to understand the following details properly.
Note 2:
I have tried to use mounted property of the state to determine if it can be used or not but it shows true for both widgets (MainPage TextDisplay and NewPage TextDisplay)
Let me know in the comments if more details or I missed something which is required.
Use the following sample code with provider dependency included for reproducing the problem:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
// need to detect if this widget is on the screen,
// only then we should go ahead with this long process
// otherwise we should skip this long process
doLongProcess(widget.name);
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
);
}
void doLongProcess(String name) {
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}
Posting solution for others to refer.
Refer to this flutter plugin/package:
https://pub.dev/packages/visibility_detector
The solution code:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
/// this holds the latest known status of the widget's visibility
/// if [true] then the widget is fully visible, otherwise it is false.
///
/// Note: it is also [false] if the widget is partially visible since we are
/// only checking if the widget is fully visible or not
bool _isVisible = true;
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
/// This is the widget which identifies if the widget is visible or not
/// To my suprise this is an external plugin which is developed by Google devs
/// for the exact same purpose
child: VisibilityDetector(
key: ValueKey<String>(widget.name),
onVisibilityChanged: (info) {
// print('\n ------> Visibility info:'
// '\n name: ${widget.name}'
// '\n visibleBounds: ${info.visibleBounds}'
// '\n visibleFraction: ${info.visibleFraction}'
// '\n size: ${info.size}');
/// We use this fraction value to determine if the TextDisplay widget is
/// fully visible or not
/// range for fractional value is: 0 <= visibleFraction <= 1
///
/// Meaning we can also use fractional values like, 0.25, 0.3 or 0.5 to
/// find if the widget is 25%, 30% or 50% visible on screen
_isVisible = info.visibleFraction == 1;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
/// now that we have the status of the widget's visiblity
/// we can skip the long process when the widget is not visible.
if (_isVisible) {
doLongProcess(widget.name);
}
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
),
);
}
void doLongProcess(String name) {
print('\n ============================ \n');
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
final element = widget.createElement();
print('\n name: ${widget.name}'
'\n element: $element'
'\n owner: ${element.state.context.owner}');
print('\n ============================ \n');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}

How to make pagination in case items as horizontal Scrolling not vertical listview builder in flutter?

I have horizontal listview builder so how to make pagination to load more data in case items for horizontal listview !
You can copy paste run full code below
You can use package https://pub.dev/packages/flutter_pagewise
code snippet
return PagewiseListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
pageSize: PAGE_SIZE,
itemBuilder: this._itemBuilder,
pageFuture: (pageIndex) =>
BackendService.getPosts(pageIndex * PAGE_SIZE, PAGE_SIZE));
working demo
full code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_pagewise/flutter_pagewise.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class PagewiseListViewExample extends StatelessWidget {
static const int PAGE_SIZE = 5;
#override
Widget build(BuildContext context) {
return PagewiseListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
pageSize: PAGE_SIZE,
itemBuilder: this._itemBuilder,
pageFuture: (pageIndex) =>
BackendService.getPosts(pageIndex * PAGE_SIZE, PAGE_SIZE));
}
Widget _itemBuilder(context, PostModel entry, _) {
return Container(
width: 200,
child: ListTile(
leading: Icon(
Icons.person,
color: Colors.brown[200],
),
title: Text(entry.title),
//subtitle: Text(entry.body),
),
);
}
}
class BackendService {
static Future<List<PostModel>> getPosts(offset, limit) async {
final responseBody = (await http.get(
'http://jsonplaceholder.typicode.com/posts?_start=$offset&_limit=$limit'))
.body;
// The response body is an array of items
return PostModel.fromJsonList(json.decode(responseBody));
}
static Future<List<ImageModel>> getImages(offset, limit) async {
final responseBody = (await http.get(
'http://jsonplaceholder.typicode.com/photos?_start=$offset&_limit=$limit'))
.body;
// The response body is an array of items.
return ImageModel.fromJsonList(json.decode(responseBody));
}
}
class PostModel {
String title;
String body;
PostModel.fromJson(obj) {
this.title = obj['title'];
this.body = obj['body'];
}
static List<PostModel> fromJsonList(jsonList) {
return jsonList.map<PostModel>((obj) => PostModel.fromJson(obj)).toList();
}
}
class ImageModel {
String title;
String id;
String thumbnailUrl;
ImageModel.fromJson(obj) {
this.title = obj['title'];
this.id = obj['id'].toString();
this.thumbnailUrl = obj['thumbnailUrl'];
}
static List<ImageModel> fromJsonList(jsonList) {
return jsonList.map<ImageModel>((obj) => ImageModel.fromJson(obj)).toList();
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(height: 200, child: PagewiseListViewExample()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Creating a copy of an instance variable containing a List in DART

I have seen a few questions on this topic but have not found an answer. Given the following code I am trying to create a copy of the "kids" List from one instance to another. For Strings this works however for List it is only a reference and when I update the value of kids in the 2nd instance it also updates in the first. My code is below.
import 'package:flutter/material.dart';
import './screens/detail_screen.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: 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> {
Person person1 = Person();
List<Person> people = [];
#override
void initState() {
super.initState();
person1.firstName = 'Jack';
person1.kids = [
{'Karen': 'girl'},
{'Bob': 'girl'}
];
people.add(person1);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
itemCount: people.length,
itemBuilder: (context, int index) {
return ListTile(
title: Text(
'${people[index].firstName}',
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(people[index]),
),
);
},
);
},
),
);
}
}
class Person {
String firstName;
List kids;
}
class DetailsScreen extends StatefulWidget {
final Person person;
DetailsScreen(this.person);
#override
_DetailsScreenState createState() => _DetailsScreenState();
}
class _DetailsScreenState extends State<DetailsScreen> {
static final TextEditingController _textController = TextEditingController();
Person newPerson = Person();
#override
void initState() {
super.initState();
newPerson.firstName = widget.person.firstName;
_textController.text = widget.person.firstName;
}
updateName(newName) {
newPerson.firstName = newName;
// This is where I am trying to copy the value of "kids".
newPerson.kids = List.from(widget.person.kids);
newPerson.kids[1]['Bob'] = 'boy';
print(newPerson);
print(widget.person);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
),
onPressed: () {
Navigator.pop(context);
}),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.check,
color: Colors.white,
),
onPressed: () {
updateName(_textController.text);
}),
],
),
body: Center(
child: TextField(
controller: _textController,
),
),
);
}
}
Is there a way of doing this? If not what is best practice to achieve the same result? I have read this article but it refers to Strings and not Lists.
How to clone (copy values) a complex object in Dart 2
Can you try this:
First import dart convert top of the page like this
import 'dart:convert';
Then update your updateName method with this one
updateName(newName) {
newPerson.firstName = newName;
List<dynamic> newPersonKids = jsonDecode(jsonEncode(widget.person.kids));
newPerson.kids = newPersonKids;
newPerson.kids[1]['Bob'] = 'boy';}
List.from(list) should work for creating a new instance of the list.
You can try this in the https://dartpad.dev/
final list = [0,1,2];
final newList = list;
print(newList);
list[0] = 55;
print(newList); //here, newList is changed because it is an instance of list
Result:
[0,1,2]
[55, 1, 2]
What you would expect:
final list = [0,1,2];
final newList = List.from(list);
print(newList);
list[0] = 55;
print(newList); //here, newList has no change because it is a new instance
Result:
[0,1,2]
[0,1,2]

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.