Flutter redux: StoreConnector vs StoreProvider - flutter

I've been using flutter_redux for only very few days and I'm wondering what's the difference between:
class BtnCustom extends StatelessWidget {
#override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
return FlatButton(
onPressed: store.dispatch(MyCustomAction),
child: Text(store.state.MyCustomTxt),
);
}
}
and
class BtnCustom extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StoreConnector<AppState, _ViewModel>(
converter: (store) => _ViewModel(
txt: store.state.MyCustomTxt,
onPressed: store.dispatch(MyCustomAction)),
builder: (BuildContext context, _ViewModel vm) {
return FlatButton(
onPressed: vm.onPressed,
child: Text(vm.txt),
);
},
);
}
}
class _ViewModel {
final String txt;
final void Function() onPressed;
_ViewModel({this.txt, this.onPressed});
}
?
The first one seems so handy to use. Are they any advantages or drawbacks of using one over another I should be aware of?
According to the documentation, the StoreConnector will rebuild the widget in it so:
Is it ok to not use a StoreConnector when you don't need to rebuild a widget?
Is it ok to have multiple widgets within a StoreConnector?

StoreConnector gives you more control over widget, especially when you don't want to rebuild it. StoreConnector:
lets you detect whether the ViewModel has changed (whether widget should be rebuilt) if you use distinct: true and override hashCode and == in your ViewModel;
lets you skip widget rebuild altogether by quickly checking some specific store.state:
StoreConnector<AppState, MyViewModel>(
distinct: true,
ignoreChange: (state) {
return state.someVariable == theValueIDontCareAbout;
},
...
),
class MyViewModel{
#override
bool operator ==(other) {
return (other is MyViewModel) && (this.someVmVariable == other.someVmVariable);
}
#override
int get hashCode {
int result = 17;
result = 37 * result + someVmVariable.hashCode;
return result;
}
}
And some more fine-grained controls. Take a look at the documentation of StoreConnector's constructor. If multiple widgets inside store sonnector share the same ViewModel it is natural to have them like that. However, if it's possible to prevent them from rebulding by separating their ViewModels you can use separate StoreConnectors.

Related

Is it possible to share and update one screen's reactive value in another screen without Provider?

So I have this block of code in a widget that navigates to another screen:
screen_one.dart
class ScreenOne extends StatefulWidget {
const ScreenOne({ super.key });
#override
State<ScreenOne> createState() => _ScreenOneState();
}
class _ScreenOneState extends State<ScreenOne> {
List<String> state = [''];
#override
Widget build(BuildContext context) {
return Column(
MaterialButton(
onPressed: () => Navigator.pushNamed(context, '/screen-two'),
child: Text('Click here.')
),
Text(state[0]),
);
}
}
screen_two.dart
class ScreenTwo extends StatelessWidget {
const ScreenTwo({ super.key });
#override
Widget build(BuildContext context) {
return Container();
}
}
Basically I need to pass the state variable from ScreenOne to ScreenTwo and then update it there (in ScreenTwo)
ScreenTwo needs to display the same thing as ScreenOne and add() a new item to the state list when some button is clicked which should show on both the screens.
Its just one simple List so I am trying to avoid using provider.
Is it possible to do though?
I'm currently just passing it through the Navigator:
screen_one.dart
Navigator.pushNamed(
context,
'/post-info',
arguments: state,
),
screen_two.dart
Widget build(BuildContext context) {
final List<String> post = ModalRoute.of(context)!.settings.arguments as List<String>;
// ...
}
first I want to recommend you when things go bigger and more complex, it's better to use a state management approach, However since you did say that you have only one List you can simply use a ValueNotifier, with ValueListenableBuilder:
// this should be outside widget classes, maybe in a custom-made class or just in a global scope.
ValueNotifier stateNotifier = ValueNotifier([""]);
now in the places you want to use that state, you can use ValueListenableWidget like this:
ValueListenableBuilder(
valueListenable: stateNotifier,
builder: (context, value, child) {
return Column(
children: [
Text('${state[0]}'),
MaterialButton(
onPressed: () {
Navigator.pushNamed(context, '/screen-two'),
},
child: Text('click'),
),
],
);
},
);
}
}
and any other place where you want to see that state get updates, you need to use ValueListenableWidget.
Now, for executing a method like add() on the List and notify the widgets, you need to assign a new value for it like this:
void addInTheList(String elem) {
List current = stateNotifier.value;
current.add(elem);
// this exactly what will be responsible for updating.
stateNotifier.value = current;
}
now, you can just call addInTheList and expect it to update in all of them:
addInTheList("Example");

Widget not updating flutter

I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}

Widgets with future builder not removing widget after provider was updated with async

I have been learning flutter for 2-3 months now and I feel I have a reached a fundamental roadblock with understanding state management. This post will be long unfortunately so please bare with me and I hope I put the right detail.
Problem Definition
I have a list of widgets in a shopping cart,im at the point where I click minus and it only has 1 left the widget must be removed.No matter what I try I cant get that widget to be removed. If I click back button and go back into cart the Item will not appear anymore.
I have considered other methods, like disposing the widget(that didn't seem to work) and I was busy implementing Visibility Show/hide widgets in Flutter programmatically
but that doesn't feel like the right way.If my understanding of providers,changeNotifiers,async and future builders,is correct the below method should work and I think its fundamental to my flutter journey to understand why it doesn't work.
Overview:The idea was to use the minus button on CartItemWidget to call a method that updates Json stored on the local device, then repopulate the List cartProdList in ProductProvider which calls
notifyListeners() and then should propagate everywhere the provider is used. Now I have used this pattern successfully 5 times now, the only different this time is it will be removing a widget which I haven't done before. But this should work dynamically if the future is based of the same provider right ?
function call order
CartItemWidget.onPressed:()
calls >>>
ProductProvider.cartMinusOne(String id)
calls >>>
ProductProvider.Future<List<Product>> cartProducts()
well here goes the code.I also wouldn't mind comments on things I could be doing better in all areas.
CartWidget
class CartWidget extends StatefulWidget {
#override
_CartWidgetState createState() => _CartWidgetState();
}
class _CartWidgetState extends State<CartWidget> {
var providerOfProd;
ProductProvider cartProdProvider = new ProductProvider();
#override
void initState() {
_productsList = new ProductsList();
super.initState();
providerOfProd = Provider.of<ProductProvider>(context, listen: false).cartProducts();
}
#override
Widget build(BuildContext context) {
........
Column(children: <Widget>[
FutureBuilder(
future: providerOfProd,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Container(
width: 0,
height: 0,
);
case ConnectionState.done:
return ListView.separated(
..............
},
itemBuilder: (context, index) {
return CartItemWidget(
product: cartProdProvider.cartProdList.elementAt(index),
heroTag: 'cart',
quantity: cartProdProvider.cartProdList.elementAt(index).cartqty,
key: UniqueKey(),
);
},
);
.........
CartItemWidget
class CartItemWidget extends StatefulWidget {
CartItemWidget({Key key, this.product, this.heroTag, this.quantity = 1}) : super(key: key);
// ProductProvider cartProd = new ProductProvider();
String heroTag;
Product product;
int quantity;
#override
_CartItemWidgetState createState() => _CartItemWidgetState();
}
class _CartItemWidgetState extends State<CartItemWidget> {
#override
Widget build(BuildContext context) {
return Consumer<ProductProvider>(
builder: (context, productProv, _) => InkWell(
child: Container(
.............
child: Row(
children: <Widget>[
.............
IconButton(
onPressed: () {
setState(() {
productProv.cartMinusOne(widget.product.id);
widget.quantity = this.decrementQuantity(widget.quantity);
});
}
.............
ProductProvider
class ProductProvider with ChangeNotifier {
ProductProvider() {
cartProducts();
}
List<Product> cartProdList;
cartMinusOne(String id) async {
//Code to minus item,then return as a string to save as local jason
var test = jsonEncode(cartList);
saveLocalJson(test, 'cart.json');
cartProducts();
notifyListeners();
}
Future<List<Product>> cartProducts() async {
String jsonString = await JsonProvider().getProductJson();
String cartString = await getCartJson();
var filterProdList = (json.decode(jsonString) as List).map((i) => Product.fromJson(i)).toList();
//code to get match cart list to product list
cartProdList = filterProdList.where((element) => element.cartqty > 0).toList();
notifyListeners();
return cartProdList;
}
........................

Why can't I load actual data in the expansion panels in flutter of "Only static members can be accesed in initializers"?

I looked at the official docs flutter expansion panels as a reference to building my own. I'm having trouble making them work AND work with my data. I have tried passing some of the data my widget receives in its constructor to be used when the panel list is being generated. I hope this code fragments are enough, if not I can add more.
Here is where the error pops up (when trying to reference widget.contracts)
class FinancialTabContent extends StatefulWidget {
final List<Contract> contracts;
final Person travelOfficer;
FinancialTabContent({this.contracts, this.travelOfficer});
#override
_FinancialTabContentState createState() => _FinancialTabContentState();
}
class _FinancialTabContentState extends State<FinancialTabContent> {
List<ExpandableItem> expansionPanels = generateExpansionPanels(widget.contracts);
#override
Widget build(BuildContext context) {
...
This is what I intended to use to build the panel list, it's placed inside the _FinancialTabContentState
Widget _buildPanelList() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
expansionPanels[index].isExpanded = !isExpanded;
});
},
children: expansionPanels.map<ExpansionPanel>((ExpandableItem item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
....
isExpanded: item.isExpanded,
);
}).toList(),
);
Here's the code for the expandable item
import 'contract.dart';
class ExpandableItem {
ExpandableItem({
this.isExpanded = false,
this.panelData
});
bool isExpanded;
Contract panelData;
}
List<ExpandableItem> generateExpansionPanels(List<Contract> panelsData) {
return List.generate(panelsData.length, (int index) {
return ExpandableItem(
panelData: panelsData[index],
);
});
}
Move this line to within the build method:
List<ExpandableItem> expansionPanels = generateExpansionPanels(widget.contracts);
Or do this in the initState method if you want it only done once:
class _FinancialTabContentState extends State<FinancialTabContent> {
List<ExpandableItem> expansionPanels;
#override
void initState() {
super.initState();
expansionPanels = generateExpansionPanels(widget.contracts);
}
#override
Widget build(BuildContext context) {...}
}
Read the docs on State which describe in detail the lifecycle of a Widget.

Handling variables for stateful widget

I have ListView widget whose contents are loaded dynamically.
So I decided to make myStatelessWidget.
My basic ideas are
Keep variable articles to be shown on ListView in the StatefulWidget or State.
Pass the contents from outside.
So for now, I write like this, but it has error.
Is my basic idea is correct? or where should I fix?
//// to pass the argument from outside.
new BodyLayout(articles: myarticles),
////
class BodyLayout extends StatefulWidget {
// List<Article> articles // ???I should put here/?
BodyLayout({articles});
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
// List<Article> articles // ???I should put here/?
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.articles.length, // the getter 'articles' is not defined error....
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.articles[index].title),
onTap: () => onTapped(context,widget.articles[index].url),
);
},
);
}
}
You only need to use a stateful widget if you are going to call the setState() method to rebuild the widget with some new state. One case in which you might do that, if you need to retrieve the list of articles from some api or database call, is to have the widget return a loading indicator if the articles list is null, make the async call to retrieve the articles in the state class's initState() method, and when it is returned, rebuild the widget by calling setState() with the retrieved list of articles. Like this, maybe:
/// to pass the argument from outside.
new BodyLayout(),
///
class BodyLayout extends StatefulWidget {
BodyLayout();
#override
_BodyLayoutState createState() => _BodyLayoutState();
}
class _BodyLayoutState extends State<BodyLayout>{
List<Article> articles;
bool loading = true;
#override
void initState(){
_getArticles();
}
void getArticles() async {
articles = await Repository.instance.getArticles(); //some async method to retrieve the articles
setState((){
loading = false;
}); // after the articles are retrieved you can call setState to rebuild the widget
}
#override
Widget build(BuildContext context) {
if(loading) {
return CircularProgressIndicator();
}
return ListView.builder(
itemCount: articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(articles[index].title),
onTap: () => onTapped(context, articles[index].url),
);
},
);
}
}
If you have the list of articles to begin with and don't need to rebuild the list, you can just make that a stateless widget and pass in the list of articles.
The error you indicated that you got, seems to be because articles is not actually defined as a variable for that class. Dart supports multiple syntax options for passing instance variables like this but this is how I would define that variable and ensure that it is being passed in when the widget is created (could be stateless or stateful widget):
//// to pass the argument from outside.
new BodyLayout(articles: myarticles),
////
class BodyLayout extends StatelessWidget {
final List<Article> articles
BodyLayout({this.articles}) : assert(articles != null);
#override
Widget build(BuildContext context){ ... };
}
If you want to convert your widget to a StatelessWidget, then you can just delete the createState and move the stuff in the build method of the state class into the widget class. This works just fine if your widget doesn't maintain an internal state, but if it has interactive elements (like buttons or such) you will want to delegate them to the parent widget caller via a callback.
To define properties for your custom widget, define the fields as final and instantiate them in the class constructor with this.fieldName. For example:
class BodyLayout extends StatefulWidget {
BodyLayout({
this.articles,
this.onArticleTapped,
});
final List<Article> articles; // Defining the articles property
final void Function(String) onArticleTapped; // Defining the on-tapped callback
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: widget.articles.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(widget.articles[index].title),
onTap: () => onArticleTapped(widget.articles[index].url),
);
},
);
}
}
You can then use it like such:
...
BodyLayout(
articles: [some list of articles],
onArticleTapped: (url) => <do something with url>
),