Avoid Bloc state-sharing with multiple Widget instances in Flutter - flutter

Good day everyone, I'm using Flutter with bloc and I'm trying to show a list of Channels which you can "Follow" o "Unfollow".
I have a custom ChannelContainer Widget that loads a list with channel titles and a custom widget JoinButton which shows whether the user can "Follow/Unfollow" depending on the state (If the channel is already being followed or not).
Widget _getChannelsList() {
List<Post> channelList = ContentService().getAllChannels();
return ListView.separated(
itemBuilder: (_, i) => ChannelContainer(
title: channelList[i].title,
description: channelList[i].description,
idChannel: channelList[i].id),
separatorBuilder: (_, i) => SizedBox(height: 5.0),
itemCount: channelList.length,
);
}
This is my custom JoinButton Widget:
#override
Widget build(BuildContext context) {
final channelBloc = BlocProvider.of<ChannelBloc>(context);
channelBloc.add(GetJoinStatus(idChannel));
return Container(
child: BlocBuilder<ChannelBloc, ChannelState>(builder: (context, state) {
var userIsJoined = state.userIsJoined;
if (userIsJoined) {
return ElevatedButton(
child: Text('Unfollow'),
onPressed: () {},
);
} else {
return ElevatedButton(
child: Text('Follow'),
onPressed: () {},
);
}
}),
);
}
I made a test checking the idChannel and returning true to even numbers and false to odd ones and thought that as I was creating multiple JoinButtons, each one would keep their state (followed or not) but as I'm firing the GetJoinStatus() event each time a JoinButton is instantiated and the state changes all the buttons get rebuilded and get the same state.
So, at the end I have a list of JoinButtons that are all getting just Follow or Unfollow but not mixed results.
What would be the best way to get the expected result? I've tried a lot of things that could work but anything compatible with bloc workflow... Thank you in advance!

I think the best way to do this is to have a map in your bloc state whose key is the channel id and the value is a boolean true if joined and false if not.

Related

Generic filter Flutter

Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...

Can I get a logic/code for highlighting the desired value in the list view builder in flutter

[
Can I get a logic/code for highlighting just the selected value from this list view which is inside a container and it is scrollable also that the axis is set to Horizontal.
I have used a list view builder to align the same and also generated the list of numbers.
Please check the sample image of the widget attached.
Blockquote
]1
It's hard to tell you exactly how to do it without any code examples, and I'm also not sure what you mean by selected. Is that already decided before building the list, or is it decided when the user selects from the list?
If it is already decided, you can pass a value from the parent component that tells the list to highlight a certain value.
If a user is selecting the value to highlight, you can use a combination of setState and onTap with GestureDetector. Here's a potential rough skeleton:
int selectedIndex?;
ListView.builder(
itemCount: litems.length,
itemBuilder: (BuildContext ctx, int index) {
return GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
},
child: Container(
backgroundColor: selectedIndex == index ? highlightedColor : undefined;
child: {{child content}}
),
);
}
)

How to change displayed data the best way in Flutter

i want to change displayed data in Flutter? I wrote a function changeDataForTest (only a function for testing the event), which should change the data displayed in Text.
But if I click on this, it isn't changed. The value of the displayed string only changes, if i add (context as Element).reassemble(); after calling the method. Is this the normal way to go, or is there a smoother way to solve my problem?
dynamic changeDataForTest(neuerWert) {
this.data = neuerWert;
}
Column(
children: [
Center(
child: Text(
this.data + this.wiegehts,
),
),
FlatButton(
textColor: Color(0xFF6200EE),
onPressed: () {
changeDataForTest('neuerWert');
(context as Element).reassemble();
},
)
],
)
Thanks
Lukas
If you're using only a small widget, you could use a StatefulWidget using the method:
setState(() {
// change your variable
})
If your widget is complex and has lots of different possible variables, I'll not recommend using setState as this method calls the build method every time is being used.
One simple and fast option, is to use ValueNotifier:
final myVariable = ValueNotifier(false); // where you can replace 'false' with any Object
and then, using it this way:
ValueListenableBuilder(
valueListenable: myVariable,
builder: (context, value, child) {
return Text(value); // or any other use of Widgets
},
);
myVariable.value = true; // if you're looking for to change the current value
finally, if you logic is truly complex and you need to scale, I'll recommend to use a StateManagement library like:
Provider
Riverpod
BloC
Others
You can find those libraries and examples over: https://pub.dev

ListView renders multiple widget after calling setState in Flutter

I have this function that shows a showDialog widget with a child of ListView builder based on a list
My problem is that when I add items to the list using setState(In my case, stateSetter), the ListView re-render duplicated widget.
As I'm debugging the issue, I noticed that it only happens when I'm using a Column/Row widget.
//result in listview
(1st setState)
Product1
(2nd setState)
Product1
Product2
Product1
Product2
Code below:
void showProductDialog() {
//variable for ListViewBuilder
List<Product> _products = [];
showDialog(
//..context,builder, etc...
return AlertDialog(
//..title, etc..
content: StatefulBuilder(builder: (ctx, stateSetter) {
return Container(
child:Column(
children: [
ListView.builder(
itemCount: _products.length,
itemBuilder:(ctx,index) {
//problem occurs
return Column(children: _products.map((e) => Text(e.name)).toList()
}
),
RaisedButton.icon(
onPressed: () {
stateSetter((){
// adding new item to list
_products.add(Products(...))
})
...
Any help and suggestion will be much appreciated!
Thankyou!
EDIT: The list variable are behaving properly, when I print all the items, all of it are unique. The duplicate issue was only occured on rendering the items to Column
EDIT 2: The purpose of using column widget is to print data somehow properly.
Example of what to expect:
Product1
Name: Sample name
Price: $1
Product2
Name: Sample 2
Price: $2
i cant see the whole code but try this
Just clear the list before adding again
use
stateSetter((){
// clear the list to prevent duplication
_products.clear(),
// adding new item to list
_products.add(Products(...))
})
Sorry, my problem. I did not read your request carefully
My solution:
ListView.builder(
itemCount: _products.length,
itemBuilder:(_,index) {
return Text(_products[index].name); // remove Column
}
),

Flutter Bloc , Bloc state , navigate?

what I’m facing now is after I implemented bloc following one of the tutorials, I'm stuck now in place where after I'm getting the response and the state is changed, I want to navigate to another widget
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(APP_TITLE),
),
body: buildBody(context));
}
}
BlocProvider<SignInBloc> buildBody(BuildContext context) {
return BlocProvider(
create: (_) => sl<SignInBloc>(),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[
BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
if(state is Empty)
return MessageDisplay(message: 'Sign In please.',);
else if(state is Loaded)
return HomePage();
else
return MessageDisplay(message: 'Sign In please.',);
}
),
SignInControls(),
],
),
),
),
);
}
in state of loaded I want to navigate to another widget.
so how to achieve that, and what is the best way for it?
You can't use the navigator or change the state while the widget is being built (your case).
There're two ways
1. The old fashioned way
WidgetsBinding.instance.addPostFrameCallback((_){
// Your code goes here
});
2. Since you already implemented the BLOC library you have a more elegant way to achieve this by using BlocListener. you can learn more about it in the documentation
Hope i helped!
Navigation can be used like Inherited widgets:
Navigator nav = Navigator.of(this.context);
then you can use somthing like:
nav.push(MaterialPageRoute(builder: (context) => YourSecondPage()))
in flutter, you can't just move to some page directly. you should use a route.
I think the cleanest way to use named routes. this is an example:
// here you put a class of names to use later in all of your project.
class RouteNames{
static String homepage = "/";
static String otherPage= "/otherpage";
}
// in your main file , MyApp class
var routes = {
RouteNames.homepage: (context)=> new MyHomePage(),
RouteNames.otherPage: (context)=> new MyOtherPage()
};
// then use routes variable in your MaterialApp constructor
// and later on in your project you can use this syntax:
Navigator.of(context).pushNamed(RouteNames.otherPage);
I think this way is clean and it's centralized, it's good if you want to send arguments to routes.
To learn more about navigation: navigation official documentation is pretty good
A note about the Bloc builder & listener:
Since BlocBuilder is going to be called lots of times. it should only contain widgets and widgets only. if you put navigation code inside it, this code would be called multiple times.
As Ayham Orfali said You definitely should use BlocListener for that. Inside it you can listen to changes in state. here is an example
// some code
children: <Widget>[
BlocListener(
bloc: BlocProvider.of<SignInBloc>(context),
listener: (context, state) {
if(state is Loaded){
Navigator.of(context).pushNamed("some other page");
}
// else do nothing!
},
child:// just bloc builder which contains widgets only. ,
SignInControls(),
]
// some other code