-1

I have the following code, to get initial data for the screen and this SchedulerBinding seems to be a hack, but if I remove it, request data is lost.

I think it happens due to the fact widgets(streamBuilders etc.) are not yet built.

Any ideas how can I fix this?

enter image description here

Full screen code: https://gist.github.com/Turbozanik/7bdfc69b36fea3dd38b94d8c4fcdcc84

Full bloc code: https://gist.github.com/Turbozanik/266d3517a297b1d08e7a3d7ff6ff245f

Void
  • 1,129
  • 1
  • 8
  • 21

2 Answers2

0

You can load your data asynchronously in the initState method, meanwhile you can show a loader or message. Once your data has loaded, call setState to redraw the widget.

Here is an example of this:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  createState() => new MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  String _data;
  
  Future<String> loadData() async {
    // Simulate a delay loading the data
    await Future<void>.delayed(const Duration(seconds: 3));

    // Return the data
    return "This is your data!";
  }
  
  @override
  initState() {
    super.initState(); 

    // Call loadData asynchronously   
    loadData().then((s) { 
      // Data has loaded, rebuild the widget
      setState(() { 
        _data = s; 
      });
    });
  }
  
  @override
  Widget build(BuildContext context) {
    if (null == _data) {
      return Text("Loading...");
    }
    
    return Text(_data);
  }
}

You can test it in https://dartpad.dartlang.org

It works like this:

  1. initState will call loadData asynchronously, then the build method will draw the widget.
  2. when loadData returns, the call to setState will redraw the widget.

Using StreamBuilder

The following example uses a StreamBuilder to show the data, once it's loaded:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  createState() => new MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  // Create a stream and execute it
  final Stream<String> _myStream = (() async* {
    // Simulate a delay loading the data
    await Future<void>.delayed(const Duration(seconds: 3));
    
    // Return the data
    yield "This is your data!";
  })();
  
  @override
  Widget build(BuildContext context) {
    return StreamBuilder<String>(
      stream: _myStream,
      builder: (BuildContext context, s) {  
        String result;
        
        if (s.hasError) {
          result = "Error";
        }
        else {
          if (s.connectionState == ConnectionState.done) {
            result = s.data; 
          }
          else {
            result = "Loading...";
          }
        }
        
        return Text(result);
      }
    );
  }
}

Hope this helps :)

Alex Domenici
  • 641
  • 9
  • 14
  • It would work if I would use states, but I update ui using streams and streamBuilders, so when I receive new data, it should be updated, but it is not working like this. – Void Mar 23 '21 at 11:30
  • @Void maybe you should post your code instead of a screenshot. – Alex Domenici Mar 23 '21 at 11:45
  • Give me a moment I will create a gist – Void Mar 23 '21 at 11:48
  • Updated initial question, take a look – Void Mar 23 '21 at 11:52
  • @Void your Bloc never yields a result..? I'm changing my sample code to use StreamBuilder, give me a few minutes. – Alex Domenici Mar 23 '21 at 12:22
  • No, bloc works just fine, the only problem I have is that if I make a call when my screen is created, data is lost. – Void Mar 23 '21 at 12:24
  • @Void I've added a sample code using StringBuilder. See if it can be of help. – Alex Domenici Mar 23 '21 at 12:29
  • It seems like I am doing all as you showed, except it's a network call. – Void Mar 23 '21 at 12:35
  • @Void are you missing a 'yield' statement (after the network call returns) that can be listened to by the StreamBuilder, or maybe you forgot to await the network call itself? – Alex Domenici Mar 24 '21 at 02:15
  • I have added timestamp logs all over the place, and yep, request is finished before build method is finished. For now, I fixed it using BehaviorSubject, it saves last data, and plays a state role here, yet it's still a bit hacky in my taste. But I can't see any way we can synchronize such network requests with widget tree rendering except for SchedulerBinding which actually "does something, after build method was finished". Don't like it, but have no better solution so far. – Void Mar 24 '21 at 08:51
0

SchedulerBining is not a hack,according to docs addPostFrame call callback only once and if you remove it your stream will never get the data but you can call your stream loading in iniState

void initState(){
 super.initState();
 _mblock.loadSpotMock();

}
Dali Hamza
  • 568
  • 1
  • 4
  • 8