Handling variables for stateful widget - flutter

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>
),

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");

Access a parent class variable in its child in Flutter?

I am trying to use a custom statefull PageWrapper widget to wrap all my pages. The idea is to make it return a Scaffold and use the same menu drawer and bottom navigation bar, and call the appropriate page as page parameter.
My bottomNavigationBar is working well and I am setting the correct selectedIndex, but I can't find a way to access it in the child page (that is in another file), since I don't know how to access the parent's selectedIndex and display the appropriate widget from my page's list.
import 'package:flutter/material.dart';
class PageWrapper extends StatefulWidget {
final Widget page;
final AppBar appBar;
final BottomNavigationBar bottomNav;
final Color bckColor;
PageWrapper({#required this.page, this.appBar, this.bckColor, this.bottomNav});
#override
_PageWrapperState createState() => _PageWrapperState();
}
class _PageWrapperState extends State<PageWrapper> {
int _selectedIndex;
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
#override
void initState() {
super.initState();
_selectedIndex = 0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.appBar,
backgroundColor: widget.bckColor,
bottomNavigationBar: CustomBottomNavigation(selectedIndex: _selectedIndex, onItemTapped: _onItemTapped),
body: widget.page,
drawer: Drawer(...),
);
}
}
Named roots in my main.dart:
home: PageWrapper(page: HomeScreen()),
routes: {
'form': (context) => PageWrapper(page: RoomService()),
},
I would like to access that bottom navigation bar's current index somehow in my HomeScreen and RoomService screen. Is there a way to do it?
You can solve that by using a State Management tool like Provider or Bloc. To keep things simple, lets use Provider to do it.
Wrap MaterialApp with a ChangeNotifierProvider in your main.dart.
return MultiProvider(
providers: [
ChangeNotifierProvider<IndexModel>(
create: (context) => IndexModel()),
],
child: MaterialApp(...)
);
Create a model that will hold your index value:
Also, you have to override the getter and setter of index in order to call notifyListeners after its value is set. Here is an example:
class IndexModel extends ChangeNotifier {
int _index;
get index => _index;
set index(int index) {
_index = index;
notifyListeners(); //Notifies its listeners that the value has changed
}
}
Here is how you can display your data according to its index (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value it is listening to, changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<IndexModel, String>(
selector: (_, model) => model.index,
builder: (_, i, __) {
switch(i){
//do your returning here based on the index
}
},
);
}
)
}
Extra note. Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<IndexModel>(context,listen:false);
int index =model.index; //get index value
model.index=index; //set your index value
You have to pass listen:false when you aren't listening for changes. This is needed when you are accessing it in initState or in onPressed.

How to reload the page whenever the page is on screen - flutter

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

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.

Flutter bloc pattern repeat calling function many times

i am trying to use bloc pattern in flutter application i write the code inside body of build function before return Scaffold(); as following
#override
Widget build(BuildContext context) {
final ProductsController pController = Provider.of<ProductsController>(context);
pController.addProducts();
return Scaffold();
}
every thing is perfect but the function
addPrducts() calls too many times it looks the following code repeat it self many times
pController.addProducts();
here is the structure of ProductsContoller class
class ProductsController extends ChangeNotifier {
List<Products> _products=List();
AppDatabase appDB=AppDatabase();
List<Products> get products=>_products;
addProducts() {
appDB.getFromTable(AppDatabase.TBL_PRODUCTS).then((rows){
rows.forEach((row){
Products product=Products.fromJson(row);
_products.add(product);
});
notifyListeners();
});
}
}
If your function should only get called once u should try to override the initState() Method and call it there. If your class extends a StatefulWidget your build(BuildContext context) Method possibly gets called multiple times.
final ProductsController pController
#override
void initState() {
pController = Provider.of<ProductsController>(context);
pController.addProducts();
super.initState();
}
If you want to do some operation like fetching screen data only once in the stateful widget then you can make use of void didChangeDependencies() along with the boolean flag.
didChangeDependencies() is also called immediately after initState.
Also called when a dependency of this State object changes. It is safe to call BuildContext.dependOnInheritedWidgetOfExactType from this method.
final ProductsController pController
var _isLoadingForFirstTime = true;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (_isLoadingForFirstTime) {
pController = Provider.of<ProductsController>(context);
pController.addProducts();
}
_isLoadingForFirstTime = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(); // You Screen contents here
}
you must define your widget that used your list in other classes (stateful or stateless).
for example, if you use List in ListView, you must create a stateless class for your ListView and watch list in this class.
class ProductList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 260,
childAspectRatio: 3 / 4.6,
),
itemCount: context.watch<ProductsController >().productPjo.listProduct.length,
shrinkWrap: true,
itemBuilder: (_, i) {
return ItemProduct(context.watch<ProductsController >().productPjo.listProduct[i]);
},
);
}
}