So I'm trying to make a list that contains some widgets and then add a new widget to it when I press a button, but it doesn't seem to be working
This is the code:
class MessagesProvider extends ChangeNotifier{
List<dynamic> mesgs = [
new chatBubbleSend(),
new chatBubbleReceiver(),
new chatBubbleReceiver()
];
bool loading = true;
addMesg(){
mesgs.add(chatBubbleSend());
print(mesgs.length);
print(mesgs);
notifyListeners();
}
printMesg(){
print(mesgs.length);
print(mesgs);
}
removeMesg(){
mesgs.removeLast();
print(mesgs.length);
print(mesgs);
notifyListeners();
}
}
and this is what i get when i press the add, remove or print buttons
add,remove,print
and this is the list builder code
ChangeNotifierProvider<MessagesProvider>(
create: (context) => MessagesProvider(),
child: ChatMessages()
),
class ChatMessages extends StatelessWidget {
#override
Widget build(BuildContext context) {
final mesgs = Provider.of<MessagesProvider>(context, listen: false).mesgs;
return ListView.builder(
shrinkWrap: true,
itemCount: mesgs.length,
itemBuilder: (context,index)=> mesgs[index],
);
}
}
I have looking for a solution for over 8 hours now, and still, I couldn't fix it.
I jumped the gun with my first answer sorry.
When trying to recreate I ran into the same frustrating issue - focusing on the the provider being the problem until I realised it's actually the rendering of the updated list that's the issue.
You need to use a list builder to render the updating list in a change notifier consumer in a stateful widget
Full working example below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class WidgetListProvider with ChangeNotifier {
List<Widget> widgets = [];
int listLength = 0;
void addWidget(){
Widget _widget = Text('Hello');
widgets.add(_widget);
listLength = widgets.length;
print('Added a widget');
notifyListeners();
}
void removeWidget(){
if (widgets.length > 0) {
widgets.removeLast();
listLength = widgets.length;
print('Removed a widget');
notifyListeners();
}
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget _appBar (BuildContext context) {
return AppBar(
title: Text('My App'),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(context),
// You need to define widgets that update when a provider changes
// as children of a consumer of that provider
body: Consumer<WidgetListProvider>(builder: (context, widgetProvider, child){
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
child: Text('Add widget'),
onPressed: () {
widgetProvider.addWidget();
},
),
RaisedButton(
child: Text('Remove Widget'),
onPressed: () {
widgetProvider.removeWidget();
},
),
Row(
children: [
Text('Number of Widgets: '),
Text(widgetProvider.listLength.toString()),
],
),
Container(
height: MediaQuery.of(context).size.height*0.6,
child: ListView.builder(itemCount: widgetProvider.widgets.length, itemBuilder: (BuildContext context, int index){
return widgetProvider.widgets[index];
})
)
],
),
);
}
),
);
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => WidgetListProvider(),
child: MyApp(),
)
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: HomePage(),
);
}
}
Related
I want to refresh the state when calling Navigator Pop / Navigator Pop Until.
While I was doing some research, I finally found this article Flutter: Refresh on Navigator pop or go back. From the code in the article, it can work fine.
But there is a problem when I use the widget tree, for example like the code below:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Refresh on Go Back',
home: HomePage(),
);
}
}
Home Page - Parent Class
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int id = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Data: $id',
style: Theme.of(context).textTheme.headline5,
),
ButtonWidget(),
],
),
),
);
}
void refreshData() {
id++;
}
onGoBack(dynamic value) {
refreshData();
setState(() {});
}
}
Button Widget - Widget Class
class ButtonWidget extends StatelessWidget{
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
SecondPage())).then(onGoBack);
// The Problem is Here
// How to call a Method onGoBack from HomePage Class
}
);
}
}
SecondPage
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);
},
child: Text('Go Back'),
),
),
);
}
}
Or is there another solution to refresh the state class when calling Navigator Pop / Navigator Pop Until?
re-write your Button's class like this:
class ButtonWidget extends StatelessWidget{
final Function onGoBack;
ButtonWidget({this.onGoBack})
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) =>
SecondPage())).then(onGoBack);
//to avoid any np exception you can do this: .then(onGoBack ?? () => {})
// The Problem is Here
// How to call a Method onGoBack from HomePage Class
}
);
}
}
And add the onGoBack function as a parameter from the home page like this:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int id = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Data: $id',
style: Theme.of(context).textTheme.headline5,
),
ButtonWidget(onGoBack: onGoBack),
],
),
),
);
}
void refreshData() {
id++;
}
onGoBack(dynamic value) {
refreshData();
setState(() {});
}
}
you must sent function on widget
class ButtonWidget extends StatelessWidget{
final Function(dynamic)? refresh;
const ButtonWidget({this.refresh})
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: ()async {
await Navigator.push(context, MaterialPageRoute(builder: (context) =>
SecondPage()));
if(refresh!=null){
refresh!("your params");
}
// The Problem is Here
// How to call a Method onGoBack from HomePage Class
}
);
}
}
and you can use widget
ButtonWidget(
refresh:onGoBack
)
Try this, it just you are calling method out of scope
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Refresh on Go Back',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int id = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'Data: $id',
style: Theme.of(context).textTheme.headline5,
),
ButtonWidget(
refresh: onGoBack,
)
],
),
),
);
}
void refreshData() {
id++;
}
onGoBack(dynamic value) {
refreshData();
setState(() {});
}
}
class ButtonWidget extends StatelessWidget {
final Function(dynamic)? refresh;
ButtonWidget({Key? key, this.refresh}) : super(key: key);
#override
Widget build(BuildContext context) {
print(refresh);
return RaisedButton(onPressed: () async {
await Navigator.push(
context, MaterialPageRoute(builder: (context) => SecondPage()))
.then((value) => refresh!("okay"));
});
}
}
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);
},
child: Text('Go Back'),
),
),
);
}
}
I'm new to flutter and have a question about navigator.
I have 2 views one called Home and List. I created a drawer that is persistent in these two views. In each view I'm creating a reference to Firebase using FutureBuilder. The problem I'm running into is that every time I go to either Home or List initState is being called again. I believe the problem comes from selecting the page from the drawer. My question How can I still move to different pages without having to called InitState everytime I change screens.
title: Text('Go to page 1'),
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Listdb()));
This is where I think the screen rebuilds itself. Is there a way to avoid rebuilding?
Thank you for your help!
You can use the AutomaticKeepAliveClientMixin to prevent reloading everytime you change page, combining with PageView for better navigation. I'll included an example here:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final PageController _pageController = PageController();
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
_pageController.jumpToPage(0);
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
_pageController.jumpToPage(1);
Navigator.pop(context);
},
),
],
),
),
body: PageView(
controller: _pageController,
children: <Widget>[
PageOne(),
PageTwo(),
],
),
);
}
}
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> with AutomaticKeepAliveClientMixin {
#override
void initState() {
print("From PageOne - This will only print once");
super.initState();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: Colors.red,
);
}
}
class PageTwo extends StatefulWidget {
#override
_PageTwoState createState() => _PageTwoState();
}
class _PageTwoState extends State<PageTwo> with AutomaticKeepAliveClientMixin {
#override
void initState() {
print("From PageTwo - This will only print once");
super.initState();
}
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
backgroundColor: Colors.blue,
);
}
}
This is code:
main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Something>(
create: (_) => Something(),
child: Consumer<Something>(
builder: (BuildContext context, Something value, Widget child) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
},
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String mockData = '';
#override
void initState() {
super.initState();
initData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'moceData:$mockData',
),
Text(
'${Provider.of<Something>(context).count}',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return SecondPage();
}));
},
child: Icon(Icons.add),
),
);
}
initData() {
Future.delayed(Duration(seconds: 1), () {
mockData = 'mock 123';
setState(() {});
});
}
}
SecondPage
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: GestureDetector(
onTap: () {
Provider.of<Something>(context, listen: false).doSomething();
},
child: Text('click'),
),
),
),
);
}
}
Something
class Something extends ChangeNotifier {
var count =0;
void doSomething() {
print('doSomething');
count++;
notifyListeners();
}
}
when we open this app, MyHomePage request data in initState,
when we push secondPage,we click ‘click’ btn,We want the first page to retrieve the data(iniData()).
when we click ,notifiyListeners() and _MyHomePageState build()is called, but initState()is not,so
how to do?we can invoke initData again.
Similar situation:
1.We have changed the language on other pages. The data on the home page needs to re-request the language interface of the response.
2.After the user logs in successfully, refresh the user inventory, the inventory page already exists
Try this :
setState(() {
mockData = 'mock 123';
});
But here you are not initializing data to use it with Provider, if you are looking to get data ( i mean mockData var ) with Provider , you can do that :
in Something class you add this:
String mockData="123";
String get mockdata => mockData;
and then in the HomePage you access this data using the Provider :
Provider.of<Something>(context, listen:false).mockdata;
i hope i could help you.. good luck !
sorry,Maybe I didn't describe the problem clearly enough, but I have found a solution now.
use
ChangeNotifierProxyProvider<Foo, MyChangeNotifier>(
create: (_) => MyChangeNotifier(),
update: (_, foo, myNotifier) => myNotifier
..foo = foo,
child: ...
);
/// A [ChangeNotifierProvider] that builds and synchronizes a [ChangeNotifier]
/// from values obtained from other providers.
Thanks
The basic outline of the widget is shown Below. MyListViewBuilder1 and MyListViewBuilder2 both stateful widget are defined in separate dart file. Both of them consists of ListViewBuilder.
What I want to achieve is that when the item of MyListViewBuilder1 gets deleted then deleted item appears in MyListViewBuilder2, But the problem is this happens only when I restart the screen.
So how can I change solve this?
How can I change the state of next when state of one is changed?
Column(
children: <Widget>[
MyListViewBuilder1(),
MyListViewBuilder2()
]
)
You could use the provider package to manage state in different widgets throughout the application. In the below example, when an item is deleted in the MyListViewBuilder1, it is removed from the list and added to the list of deleted items in the ItemChangeNotifier class. The MyListViewBuilder2 has its own independent list of items, however in its build method it watches for any changes to the list of deleted items in the ItemChangeNofifier class and adds these deleted items to its own independent list of items.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<ItemChangeNotifier>(
create: (context) {
return ItemChangeNotifier();
},
),
],
child: MyApp(),
),
);
}
class Item {
int id;
String title;
Item({
this.id,
this.title,
});
}
class ItemChangeNotifier extends ChangeNotifier {
final _deletedItems = <Item>[];
List<Item> get deletedItems => List.unmodifiable(_deletedItems);
void deleteItem(Item item) {
_deletedItems.add(item);
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: MyListViewBuilder1(),
),
Divider(
thickness: 8,
),
Expanded(
child: MyListViewBuilder2(),
),
],
),
);
}
}
class MyListViewBuilder1 extends StatefulWidget {
#override
_MyListViewBuilder1State createState() => _MyListViewBuilder1State();
}
class _MyListViewBuilder1State extends State<MyListViewBuilder1> {
List<Item> _items;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(_items[index].title),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
context.read<ItemChangeNotifier>().deleteItem(_items[index]);
setState(() {
_items.removeAt(index);
});
},
),
);
},
itemCount: _items.length,
);
}
#override
void initState() {
super.initState();
_items = List.generate(
10,
(index) => Item(
id: index + 1,
title: 'Item ${index + 1}',
),
);
}
}
class MyListViewBuilder2 extends StatefulWidget {
#override
_MyListViewBuilder2State createState() => _MyListViewBuilder2State();
}
class _MyListViewBuilder2State extends State<MyListViewBuilder2> {
List<Item> _items;
#override
Widget build(BuildContext context) {
final items = [
..._items,
...context.select<ItemChangeNotifier, List<Item>>(
(itemChangeNotifier) => itemChangeNotifier.deletedItems,
),
];
return ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
);
},
itemCount: items.length,
);
}
#override
void initState() {
super.initState();
_items = List.generate(
10,
(index) => Item(
id: index + 101,
title: 'Item ${index + 101}',
),
);
}
}
I have a ListView in column ,and need add RefreshIndicator to the ListView,but it not work well
I tried contain the listView by Expanded,then list display well,but when call the RefreshIndicator ,app dump...
some one can help me ,how to fix this code,thanks
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
return null;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[RefreshIndicator(child:
ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
), onRefresh: _handleRefresh,)
],
)
);
}
}
I have two remarks :
You should use the Expanded widget with the flex param.
You do not need to return anything in the setState method.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
List<int> items = List.generate(16, (i) => i);
Future<Null> _handleRefresh() async {
await Future.delayed(Duration(seconds: 5), () {
print('refresh');
setState(() {
items.clear();
items = List.generate(40, (i) => i);
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Refresh"),
),
body: Column(
children: <Widget>[
Expanded(
flex: 1,
child: RefreshIndicator(
child: ListView.builder(
itemCount: items.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ListTile(
title: Text("Index$index"),
);
},
),
onRefresh: _handleRefresh,
),
)
],
),
);
}
}
The answer above( by Tarek Baz) is correct, however in some special cases (like deep/complicated widget tree) it might not be enough and you might have to pass the physics parameter to the ListView.builder() function.
ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: .... ... )