Flutter: Stateful Widget does not update - flutter

Imagine two Widgets: Main that manages a tabbar and therefore holds several Widgets - and Dashboard.
On Main Constructor I create a first Instance of Dashboard and the other tabbar Widgets with some dummy data (they are getting fetched in the meanwhile in initState). I build these with Futurebuilder. Once the data arrived I want to create a new Instance of Dashboard, but it won't change.
class _MainState extends State<HomePage> {
var _tabs = <Widget>[];
Future<dynamic> futureData;
_MainState() {
_tabs.add(Dashboard(null));
}
#override
void initState() {
super.initState();
futureData = _getData();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data != null) {
tabs[0] = Dashboard(snapshot.data);
} else {
return CircularProgressIndicator();
}
});
}
}
class DashboardScreen extends StatefulWidget {
final data;
DashboardScreen(this.data,
{Key key})
: super(key: key) {
print('Dashboard Constructor: ' + data.toString());
}
#override
_DashboardScreenState createState() => _DashboardScreenState(data);
}
class _DashboardScreenState extends State<DashboardScreen> {
var data;
_DashboardScreenState(this.data);
#override
void initState() {
super.initState();
print('InitState: ' + data.toString());
}
#override
void didUpdateWidget(Widget oldWidget) {
super.didUpdateWidget(oldWidget);
print('didUpdateWidget');
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
print('didChangeDependencies' + data.toString());
}
#override
Widget build(BuildContext context) {
return Text(data.toString());
}
}
When I print on several available methods it comes clear that the DasboardScreenState is not recreated. Only the DashboardScreen Constructor is called again when the data arrived, but not it's state...
flutter: MainConstructor: null
flutter: Dashboard Constructor: null
flutter: InitState: null
flutter: didChangeDependencies: null
flutter: Dashboard Constructor: MachineStatus.Manual <- Here the data arrived in futureBuilder
How can I force the State to recreate? I tried to use the key parameter with UniqueKey(), but that didn't worked. Also inherrited widget seems not to be the solution either, despite the fact that i don't know how to use it in my use case, because the child is only available in the ..ScreenState but not the updated data..
I could imagine to inform dashboardScreenState by using Stream: listen to messages and then call setState() - I think, but that's only a workaround.
Can anyone help me please :)?

I know I have had issues with the if statement before, try:
return FutureBuilder(
future: futureData,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) { //use hasData
DataType data = snapshot.data; //Declare Values first
tabs[0] = Dashboard(data);
} else {
return CircularProgressIndicator();
}
});

Related

Flutter: How to force didchangedependencies or InitStateto to be executed in a StatefulWidget Class

I've the following code, the code working fine. It retrieves News from DB and presents it in the ListView. The News have two types (1 and 2).
class NewsFragment extends StatefulWidget {
NewsFragment({Key? key, required this.eltype}) : super(key: key);
final int eltype;
#override State<StatefulWidget> createState() {
return new NewsFragmentState();
}
}
class NewsFragmentState extends State<NewsFragment> {
late Future <List<news_item>> futureData;
( ... )
Future <List<news_item>> fetchNews({eltype: 0, lu: 0}) async {
...
}
#override
initState() {
super.initState();
void getNewsfromDB({eltype: 0}) async {
futureData = fetchNews(eltype: eltype);
}
getNewsfromDB(eltype: widget.eltype);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder <List<news_item>> (
future: futureData,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<news_item> data = snapshot.data!;
BDS_Utilities.BDS_log('Element Type:${widget.eltype} #${data.length}', 'build');
return
Scaffold(
body: (data.length > 0)
? ListView.separated(
(...)
There is a parameter to pass to the NewsFragment class, the class is called by a Drawer ...
_getDrawerItemWidget(int pos) {
switch (pos) {
case 1:
return new NewsFragment(eltype: 0);
case 2:
return new NewsFragment(eltype: 1);
case 3:
return new MapFragment();
...
other cases ...
If new NewsFragment(eltype: 0) is called and then new NewsFragment(eltype: 1), or vice versa, nothing changes, the initState() is not called so the content is not refreshed.
If new NewsFragment(eltype: 0) is called then new MapFragment(), then new NewsFragment(eltype: 1) the initState() is invoked and the content is refreshed since it is fetched from the DB via FetchNews function.
How can I refresh the content after several continuous NewsFragment calls with different parameters? How can I tell to NewsFragment to call initState() or didChangeDependencies()? Or only if eltype is different from the actual one.

"await" in Widget build FLUTTER

I've been stuck for several hours with a problem on flutter. If you can help me that would be really nice.
I need to put "await" in my Widget build(BuildContext context){} but it's impossible to put "async".
How to do ?
When i test void _myAsyncMethod()async{} :
To Fix your issue you can put async in the body of method like this
Before=> Widget build(BuildContext context) {
After=> Widget build(BuildContext context) async{
Although this will not solve your problem as flutter wiill warn you as this is not the proper way to do it.
It's not a good practice to call await inside flutter's build method Because
Generally an apps need to run a 60 frames per second on an average hence flutter's build method we'll be called over and over to re-render the ui.
Another reason is that, doing calling await function() in build method will block your UI.
Solution
use FutureBuilder
call await auth.currentUser() in initState method
Another way to solve this is to use FutureBuilder
sample Code for 1
FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<userModel> snapshot) {
if(ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId);
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
);
sample Code for 2(stateful widget)
late UserModel;
void initState() {
UserModel user = await auth.currentUser();
}
this is very basic code but it's enough for you to get started.
Note: I've assumed userModel mentioned above is response type of auth.currentUser() you can change it accordingly.
What you want to do is not optimal but you can create a method and put your await variable in there:
late final FirebaseUser _user;
void _myAsyncMethod()async{
_user = await auth.currentUser;
}
#override
Widget build(BuildContext context) {
_myAsyncMethod();
return Scaffold(appBar: AppBar(), body: Container());
}
If your are using stateful widget you can instantiate firebase auth in initstate() method.
class testFirless extends StatefulWidget {
var currentuseid = "";
testFirless({Key? key}) : super(key: key);
#override
_testFirlessState createState() => _testFirlessState();
}
class _testFirlessState extends State<testFirless> {
#override
Widget build(BuildContext context) {
return Container();
}
// ------------------------------------>heree
#override
Future<void> initState() async {
FirebaseAuth auth = FirebaseAuth.instance;
var user = await auth.currentUser;
if (user == null) {
widget.currentuseid = user!.uid;
} else {
print('User is signed in!');
}
}
}
FutureBuilder
class fbuilder extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// --------------->
return Container(child: FutureBuilder(
builder: (BuildContext ctx, AsyncSnapshot<User> snapshot) {
if (ConnectionState.done == snapshot.connectionState) {
return Text(snapshot.data.userId.toString());
} else {
return CircularProgressIndicator();
}
},
future: auth.currentUser(),
));
}
}
in stateless or stateful widget
String currentuseid="";
class fbauth extends StatelessWidget {
const fbauth({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth auth = FirebaseAuth.instance;
// ------------------------>
auth.currentUser().then((user) {
if (user == null) {
currentuseid = user!.uid;
} else {
print('User is signed in!');
}
// other logic after the user retrieval
});
return Container();
}
}
Nb: Instead of instantiating firebase auth in every widget .you must instantiate in `void main` method

Using a FutureBuilder in a Flutter stateful widget with RefreshIndicator

I have a Flutter widget which gets data from a server and renders a List. After getting the data, I parse the data and convert it to an internal object in my application, so the function is something like this:
Future<List<Data>> getData(Thing thing) async {
var response = await http.get(Uri.parse(MY_URL));
// do some processing
return data;
}
After that, I've defined a stateful widget which calls this function and takes the future to render a List.
class DataList extends StatefulWidget {
const DataList({Key key}) : super(key: key);
#override
_DataListState createState() => _DataListState();
}
class _DataListState extends State<DataList> {
Widget createListView(BuildContext context, AsyncSnapshot snapshot) {
List<Data> values = snapshot.data;
if (values.isEmpty) {
return NoResultsWidget('No results.');
}
return ListView.builder(
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
return values[index];
},
);
}
#override
Widget build(BuildContext context) {
var data = getSomething().then((thing) => getData(thing));
return FutureBuilder(
future: data,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return CustomErrorWidget('Error');
case ConnectionState.waiting:
return LoadingWidget();
default:
if (snapshot.hasError) {
return CustomErrorWidget('Error.');
} else {
return createListView(context, snapshot);
}
}
},
);
}
}
Now, the code works just fine in this manner. But, when I try to move my data to be a class variable (of type Future<List>) that I update through the initState method, the variable just never updates. Example code below:
class _DataListState extends State<DataList> {
Future<List<Data>> data;
....
#override
void initState() {
super.initState();
updateData();
}
void updateData() {
data = getSomething().then((thing) => getData(thing));
}
....
}
I want to add a refresh indicator to update the data on refresh, and to do that I need to make my data a class variable to update it on refresh, but I can't seem to figure out how to make my data part of the state of the stateful widget and have it work. any help or guides to a github code example would be appreciated.
You need to wrap the assignment of the data variable in setState so that Flutter knows the variable changed and rebuilds your widget.
For example:
void updateData() {
setState(() {
data = getSomething().then((thing) => getData(thing));
});
}

future builder keeps rebuilding on every setstate

I am building an app which uses an api and I am using the future builder to fetch the data but the problem is when the state changes it rebuilds and I want to prevent this from happen.
Thanks,
try using this :
class Example extends StatefulWidget {
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<response> future;
#override
void initState() {
future = _asyncmethodCall();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
Future<someResponse> _asyncmethodCall() async {
// async code here
}
}
similar question: How to deal with unwanted widget build?

Flutter: How to make a sequence of http requests on a widget before build method

I have 3 classes: Users, Posts and Comments. User has many Posts and
Posts has many Comments.
I want that all data to be fetched before the widget's build method is called.
I tryed to use initState() to do this:
class FetchDataExample extends StatefulWidget {
final User _user;
FetchDataExample(this._user);
#override
_State createState() => _State(_user);
}
class _State extends State<FetchDataExample> {
final User _user;
_State(this._user);
#override
void initState() {
_user.setPosts();
super.initState();
}
#override
Widget build(BuildContext context) {
print(this._user.posts[0]);
return Container(
);
}
}
In User class I have:
void setPosts() async {
String url = 'https://jsonplaceholder.typicode.com/posts?userId=' + this.id.toString();
var request = Requester.get(url); // Returns a Future<Response>
await request.then((value) => this.posts = Post.jsonToPosts(json.decode(value.body)));
this.posts.forEach((post) => post.setComments());
print(this.posts[0]);
}
The 'setComments()' has the same logic.
I have two prints:
Inside build that returns null;
Inside setPosts the returns Instance of 'Post';
So, by the time that Build method is called in the widget, the initState has not finished yet.
I need it be finished, does anyone know how can I do that?
You can use a FutureBuilder to build a widget by using latest result from a future.
And also you can combile multiple futures into a single one using Future.wait method.
Here is a sample code:
_getPageData() async {
var _combinedFutures = await Future.wait([setPosts, setComments]);
//do stuff with data
}
...
#override
Widget build(BuildContext context) {
return FutureBuilder(
future:_getPageData(),
builder: (context, snapshot) {
return Container();
}),
);
});