When calling onClick(), how can I prevent only WidgetOne is rebuild, while WidgetTwo is not?
class SomeClass extends StatefulWidget {
#override
State<SomeClass> createState() => _SomeClass();
}
class _SomeClass extends State<SomeClass>{
int _i = 0;
#override
Widget build(BuildContext context) {
return child: Column(
children: [
WidgetOne(i: _i),
WidgetTwo()
],
)
}
void onClick() {
setState(() {
i++;
});
}
I add the following text, because otherwise stackoverflow is written "It looks like your post is mostly code; please add some more details." :(
A simple and fast solution is to put WidgetOne in StatefulBuilder like this:
class SomeClass extends StatefulWidget {
#override
State<SomeClass> createState() => _SomeClass();
}
class _SomeClass extends State<SomeClass> {
int _i = 0;
Function? innerSetState;
#override
Widget build(BuildContext context) {
return Column(
children: [
StatefulBuilder(
builder: (context, innerSetState) {
this.innerSetState = innerSetState;
return WidgetOne(i: _i);
},
),
WidgetTwo()
],
);
}
void onClick() {
innerSetState?.call(() {
_i++;
});
}
}
Another solution is to use provider state manager along with Consumer widget.
Related
I am trying to update the notifier value from parent widget whereas ValueListenableBuilder is defined in a child widget but the builder is not calling after changing the value.
Here is the parent widget code in which I have declared two child widgets as StatefulWidget and also declared a static object of Notifier class. I am calling the method updateMenuItemList from secondChild() widget like this HotKeysWidget.of(context)!.updateMenuItemList(currentCat!['items']); to update the list of firstChild() widget :
class HotKeysWidget extends StatefulWidget {
static HotKeysWidgetState? of(BuildContext context) =>
context.findAncestorStateOfType<HotKeysWidgetState>();
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return HotKeysWidgetState();
}
}
class HotKeysWidgetState extends State<HotKeysWidget> {
static DealsNotifier appValueNotifier = DealsNotifier();
updateMenuItemList(List<Food> list) {
appValueNotifier.updateMenuList(list);
}
#override
Widget build(BuildContext context) {
return Container(child: Column(children: [
firstChild(),
secondChild(),
],
),
);
}
}
Here is my Notifier class:
class DealsNotifier {
ValueNotifier<List<Food>> dealList = ValueNotifier([]);
ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
ValueNotifier<List<Map<String,dynamic>>> categoryList = ValueNotifier([]);
void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
void updateCategoryList(List<Map<String,dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
List<Food> getDealList() {
return dealList.value;
}
List<Food> getMenuitemList() {
return menuitemList.value;
}
List<Map<String,dynamic>> getCategoryList() {
return categoryList.value;
}
}
And this is the child widget named as firstChild() in parent code. Here the ValueListenerBuilder is declared:
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: HotKeysWidgetState.appValueNotifier.menuitemList,
builder: (context, List<Food> value, widget)
{
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu:currentCat != null ? value : [],
);
},
);
}
}
class secondChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return secondChildState();
}
}
class secondChildState extends State<secondChild> {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: (){
HotKeysWidget.of(context)!.updateMenuItemList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
Anyone help me with this issue please.
Thanks in advance
While there's still not enough code shared to fully reproduce your situation, I can offer some suggestions.
The state portion of StatefulWidgets are private by default for a reason. You shouldn't make them public just to access variables that are inside there are several other to access outside classes within widgets.
So anytime you're doing something like this
class firstChild extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return firstChildState();
}
}
class firstChildState extends State<firstChild> {
#override
...
Just stick to the default syntax of a StatefulWidget and also classes should be in UpperCamelCase with the first letter capitalized.
class FirstChild extends StatefulWidget {
const FirstChild({Key? key}) : super(key: key);
#override
State<FirstChild> createState() => _FirstChildState();
}
class _FirstChildState extends State<FirstChild> {
#override
Widget build(BuildContext context) {
...
If you find yourself wanting to edit this default syntax its a clue that you need to find a better way to achieve whatever it is you're trying to do. If you're need to access a function that is declared in a Widget from outside that Widget, then that function should be declared somewhere else.
All that being said, unless you need setState, initState or another of the lifecycle functions, then you don't need a StatefulWidget to begin with. All those classes can be Stateless.
An easy way to make that DealsNotifier class globally accessible without a full on state management solution is to make it a static class.
class DealsNotifier {
static ValueNotifier<List<Food>> dealList = ValueNotifier([]);
static ValueNotifier<List<Food>> menuitemList = ValueNotifier([]);
static ValueNotifier<List<Map<String, dynamic>>> categoryList =
ValueNotifier([]);
static void updateDealsList(List<Food> list) {
dealList.value = list;
print('DEAL LIST IN CLASS: ${dealList}');
}
static void updateMenuList(List<Food> list) {
menuitemList.value = list;
print('PRICE CHANGE: ${menuitemList.value[2].price}');
print('MENU ITEM LIST IN CLASS: ${menuitemList}');
}
static void updateCategoryList(List<Map<String, dynamic>> catList) {
categoryList.value = catList;
print('DEAL LIST IN CLASS: ${categoryList}');
}
static List<Food> getDealList() {
return dealList.value;
}
static List<Food> getMenuitemList() {
return menuitemList.value;
}
static List<Map<String, dynamic>> getCategoryList() {
return categoryList.value;
}
}
Then when you need to pass in the valueListenable you access via DealsNotifier.menuitemlist and its always the same instance.
return ValueListenableBuilder(
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
Here's the Stateless version of all those classes and wherever you need the UI update you can use ValueListenableBuilder and pass in DealsNotifier.whicheverVariableYouWantToListenTo in the valueListenable. Then call whichever relevant method from the DealsNotifier class ie. DealsNotifier.updateMenuList([]).
And you didn't share your HotkeysMenuItemsWidget but if that's where you're looking to see the change in the UI, then that is where the ValueListenableBuilder should be. Its currently too high up in the widget tree all it needs to do is re-render the list in that Widget, you don't need/want an entire re-build of the HotkeysMenuItemsWidget from a parent widget.
class FirstChild extends StatelessWidget {
const FirstChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder( // this should be inside HotkeysMenuItemsWidget
valueListenable: DealsNotifier.menuitemList,
builder: (context, List<Food> value, widget) {
print('MENUITEM LIST UPDATED: ${value}');
return HotkeysMenuItemsWidget(
key: menuItemsKey,
currentMenu: currentCat != null ? value : [],
);
},
);
}
}
class SecondChild extends StatelessWidget {
const SecondChild({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
DealsNotifier.updateMenuList([]);
},
child: Text(
'UPDATE',
maxLines: 2,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
),
),
);
}
}
class HotKeysWidget extends StatelessWidget {
const HotKeysWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
FirstChild(),
SecondChild(),
],
),
);
}
}
I am making a list of stateless widget as shown below and passing the id as the parameter to the widgets.
Code for cartPage:-
class Cart extends StatefulWidget {
#override
_CartState createState() => _CartState();
}
class _CartState extends State<Cart> {
bool loading=true;
List<CartTile> cartTiles=[];
#override
void initState() {
super.initState();
if(currentUser!=null)
getData();
}
getData()async
{
QuerySnapshot snapshot=await cartReference.doc(currentUser.id).collection('cartItems').limit(5).get();
snapshot.docs.forEach((doc) {
cartTiles.add(CartTile(id: doc.data()['id'],index: cartTiles.length,));
});
setState(() {
loading=false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,
body: loading?Center(child:CircularProgressIndicator():SingleChildScrollView(
child: Column(
children: cartTiles,
),
),
);
}
}
Code for CartTile:-
class CartTile extends StatelessWidget {
final String id;
CartTile({this.id,});
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: productReference.doc(id).snapshots(),
builder: (context,snapshot)
{
//here am using the snapshot to build the cartTile.
},
);
}
}
So, my question is whenever I will call setState in my homepage then will the stateless widget be rebuilt and increase my document reads. Because i read somewhere that when we pass the same arguments or parameters to a stateless widget then due to its cache mechanism it doesn't re build. If it will increase my reads then is there any other way to solve this problem?
I am new to the BLoC pattern on flutter and i'm trying to rebuild a messy flutter app using it. Currently, I intend to get a list of user's apps and display them with a ListView.builder(). The problem is that whenever the state of my AppsBloc changes, my StatelessWidget doesn't update to show the new state. I have tried:
Using MultiBlocProvider() from the main.dart instead of nesting this appsBloc inside a themeBloc that contains the whole app
Returning a list instead of a Map, even if my aux method returns a correct map
Using a StatefulWidget, using the BlocProvider() only on the ListView...
I have been reading about this problem on similar projects and the problem might be with the Equatable. However, I haven't been able to identify any error on that since I'm also new using Equatable. I have been debugging the project on VScode with a breakpoint on the yield* line, and it seems to be okay. In spite of that the widget doesn't get rebuilt: it keeps displaying the textcorresponding to the InitialState.
Moreover, the BLoC doesn't print anything on console even though all the states have an overwritten toString()
These are my 3 BLoC files:
apps_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:device_apps/device_apps.dart';
import 'package:equatable/equatable.dart';
part 'apps_event.dart';
part 'apps_state.dart';
class AppsBloc extends Bloc<AppsEvent, AppsState> {
#override
AppsState get initialState => AppsInitial();
#override
Stream<AppsState> mapEventToState(AppsEvent event) async* {
yield AppsLoadInProgress();
if (event is AppsLoadRequest) {
yield* _mapAppsLoadSuccessToState();
}
}
Stream<AppsState> _mapAppsLoadSuccessToState() async* {
try {
final allApps = await DeviceApps.getInstalledApplications(
onlyAppsWithLaunchIntent: true, includeSystemApps: true);
final listaApps = allApps
..sort((a, b) =>
a.appName.toLowerCase().compareTo(b.appName.toLowerCase()));
final Map<Application, bool> res =
Map.fromIterable(listaApps, value: (e) => false);
yield AppsLoadSuccess(res);
} catch (_) {
yield AppsLoadFailure();
}
}
}
apps_event.dart
part of 'apps_bloc.dart';
abstract class AppsEvent extends Equatable {
const AppsEvent();
#override
List<Object> get props => [];
}
class AppsLoadRequest extends AppsEvent {}
apps_state.dart
part of 'apps_bloc.dart';
abstract class AppsState extends Equatable {
const AppsState();
#override
List<Object> get props => [];
}
class AppsInitial extends AppsState {
#override
String toString() => "State: AppInitial";
}
class AppsLoadInProgress extends AppsState {
#override
String toString() => "State: AppLoadInProgress";
}
class AppsLoadSuccess extends AppsState {
final Map<Application, bool> allApps;
const AppsLoadSuccess(this.allApps);
#override
List<Object> get props => [allApps];
#override
String toString() => "State: AppLoadSuccess, ${allApps.length} entries";
}
class AppsLoadFailure extends AppsState {
#override
String toString() => "State: AppLoadFailure";
}
main_screen.dart
class MainScreen extends StatelessWidget {
const MainScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TabBarView(
children: <Widget>[
HomeScreen(),
BlocProvider(
create: (BuildContext context) => AppsBloc(),
child: AppsScreen(),
)
,
],
);
}
}
apps_screen.dart
class AppsScreen extends StatelessWidget {
const AppsScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
margin: EdgeInsets.fromLTRB(30, 5, 10, 0),
child: Column(children: <Widget>[
Row(
children: <Widget>[
Text("Apps"),
],
),
Row(children: <Widget>[
Container(
width: MediaQuery.of(context).size.width - 50,
height: MediaQuery.of(context).size.height - 150,
child: BlocBuilder<AppsBloc, AppsState>(
builder: (BuildContext context, AppsState state) {
if (state is AppsLoadSuccess)
return Text("LOADED");
else if (state is AppsInitial)
return GestureDetector(
onTap: () => AppsBloc().add(AppsLoadRequest()),
child: Text("INITIAL"));
else if (state is AppsLoadInProgress)
return Text("LOADING...");
else if (state is AppsLoadFailure)
return Text("LOADING FAILED");
},
),
),
])
])),
);
}
}
In GestureDetector.onTap() you create a new AppsBloc(), this is wrong. So, you need:
apps_screen.dart:
AppsBloc _appsBloc;
#override
void initState() {
super.initState();
_appsBloc = BlocProvider.of<AppsBloc>(context);
}
//...
#override
Widget build(BuildContext context) {
//...
return GestureDetector(
onTap: () => _appsBloc.add(AppsLoadRequest()),
child: Text("INITIAL")
);
//...
}
Or you can do the same even without the _appsBloc field:
BlocProvider.of<AppsBloc>(context).add(AppsLoadRequest())
I recently started to dive into Bloc Pattern in flutter, and have learned using this project.
In this project, there is an adding post feature, so I wanted to implement alike one.
Even I changed Bloc's State via Event, It doesn't add Container Widget to ListView. I tried and search this problem but I can't figure:(
Here are my code below.
Here are Bloc Parts,
#immutable
abstract class ContentsState extends Equatable {
ContentsState([List props = const []]) : super(props);
}
class ContentsLoading extends ContentsState {
#override
String toString() => 'ContentsLoading';
}
class ContentsLoaded extends ContentsState {
final List<Content> contents;
ContentsLoaded(this.contents);
#override
String toString() => 'ContentsLoaded { contents: $contents }';
}
#immutable
abstract class ContentsEvent extends Equatable {
ContentsEvent([List props = const []]) : super(props);
}
class LoadContents extends ContentsEvent {
#override
String toString() => 'LoadContents';
}
class AddContent extends ContentsEvent {
final Content content;
AddContent(this.content) : super([content]);
#override
String toString() => 'AddContent { content: $content }';
}
class ContentsBloc extends Bloc<ContentsEvent, ContentsState> {
#override
ContentsState get initialState => ContentsLoading();
#override
Stream<ContentsState> mapEventToState(ContentsEvent event) async* {
if (event is LoadContents) {
yield* _mapLoadContentsToState();
} else if (event is AddContent) {
yield* _mapAddContentToState(event);
}
}
Stream<ContentsState> _mapLoadContentsToState() async* {
final List<Content> contents = [Content('message')];
yield ContentsLoaded(contents);
}
Stream<ContentsState> _mapAddContentToState(AddContent event) async* {
if (currentState is ContentsLoaded) {
final List<Content> updateContents =
List.from((currentState as ContentsLoaded).contents)
..add(event.content);
// Output update contents
print('update contents : $updateContents');
yield ContentsLoaded(updateContents);
}
}
}
and presentation part,
class AddPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
builder: (context) => ContentsBloc()..dispatch(LoadContents()),
child: Column(
children: <Widget>[
AddBody(),
InputArea(),
],
),
);
}
}
class AddBody extends StatelessWidget {
AddBody({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final contentsBloc = BlocProvider.of<ContentsBloc>(context);
return BlocBuilder(
bloc: contentsBloc,
builder: (
BuildContext context,
ContentsState state,
) {
if (state is ContentsLoading) {
return Center(child: CircularProgressIndicator());
} else if (state is ContentsLoaded) {
final contents = state.contents;
return Expanded(
child: ListView.builder(
itemCount: contents.length,
itemBuilder: (context, index) {
final content = contents[index];
return ContentItem(content: content);
},
),
);
}
},
);
}
}
class ContentItem extends StatelessWidget {
final Content content;
ContentItem({
Key key,
this.content,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
key: Key('__Content__${content.message}__'),
child: Column(
children: <Widget>[
Text('Message Here'),
Text(content.message.toString()),
],
),
);
}
}
~~ in InputArea. There is a TextEditingController and TextField.
onTap: () => contentsBloc.dispatch(AddContent(Content(_controller.text))),
~~
Finally, this is my Content class
#immutable
class Content extends Equatable {
final String message;
Content(this.message);
#override
String toString() => 'Content { message: $message }';
}
When I run this code, I can see CircleProgressIndicator() -When State is ContentsLoading()- after that, one Container with message text shows up. So far, so good.
But After I pressed add button and fired AddContent() Event, new content doesn't show up.
Here is a console Log.
AddContent { content: Content { message: new message } }
update contents : [Content { message: message }, Content { message: new message }]
I want to add Container, but it doesn't work.
Can anyone help me? Thanks in advance:)
I resolved this problem.
I just add this one line in Bloc's ContentsLoaded class which extends ContentsState.
class ContentsLoaded extends ContentsState {
final List<Content> contents;
// just add this line below.
ContentsLoaded([this.contents = const []]) : super([contents]);
#override
String toString() => 'ContentsLoaded { contents: $contents }';
}
Thanks.
Good day. I've watched a video about Flutter's InheritedModel and got interested on it. Unfortunately, I can't seems to make it work properly.
Summary: Need help how to properly implement InheritedModel.
Expected Code Output: Widget CountText should not be updated when updating count parameter in CountModel.
Actual Code Output: CountText still updates (I think this is due to that the parent widget is a StatefulWidget)
Details
I am trying to implement a Counter app using InheritedModel. Code below is my code
import 'package:flutter/material.dart';
class CountModel extends InheritedModel<String> {
final int count;
CountModel({ this.count, child }) : super(child: child);
#override
bool updateShouldNotify(CountModel oldWidget) {
if (oldWidget.count != count) {
return true;
}
return false;
}
#override
bool updateShouldNotifyDependent(InheritedModel<String> oldWidget, Set<String> dependencies) {
if (dependencies.contains('counter')) {
return true;
}
return false;
}
static CountModel of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<CountModel>(context, aspect: aspect);
}
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Counter',
theme: Theme.of(context),
home: Counter(),
);
}
}
class Counter extends StatefulWidget {
#override
CounterState createState() => CounterState();
}
class CounterState extends State<Counter> {
int count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// title: Text("Counter"),
),
body: CountModel(
count: count,
child: CounterText()
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
++count;
});
},
child: Icon(Icons.add),
),
);
}
}
class CounterText extends StatelessWidget {
#override
Widget build(BuildContext context) {
CountModel model = CountModel.of(context, 'test');
return Text('Count: ${model.count}');
}
}
I have a CountModel as InheritedModel and a CountText widget which consumes the data from the CountModel. As you can see in the implementation of the CountText, it pass test when getting the CountModel. In my understanding, it should not be updated when the count value is updated in the CountModel. Unfortunately, this does not happen.
In short, you should use const.
Add const to CounterText constructor
class CounterText extends StatelessWidget {
const CounterText();
...
}
and use const when you create instance of CounterText() (const CounterText())
class CounterState extends State<Counter> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: CountModel(..., child: const CounterText()),
...
);
}
}
And voila 🎉
I have described why this is happening here in details