get a response from another API while FutureBuilder works - flutter

late Future<Kategori> _futureArticles;
late Future<Article> _futureSummary;
and the API's
#override
void initState() {
_futureArticles = _newsService.getArticlesByCategory(widget.id);
_futureSummary = _newsService.getArticleById(widget.id);
super.initState();
}
and FutureBuilder
child: FutureBuilder<Kategori>(
future: _futureArticles,
builder: (BuildContext context, AsyncSnapshot<Kategori> snapshot) {
if (snapshot.hasData) {
final articles = snapshot.data?.data;
now with FutureArticles and with this structure everything works but I need an another json value from _futureSummary. Both API's has got same ID values, so I can get the json.summary value from second API. But how? I tried to use future.wait but it did not work.
Meanwhile I am using second APi on different page to get all informations of a spesific news.
What is the correct approach?

Not sure what you are trying to achieve. Do you want your Future builder to rebuild only when both futures completed? If so - try to combine both futures. Future.wait will wait for all Future objects you pass to complete, and return List of results:
Let me update my answer with the working demo - you can test it in DartPad. Note that the first Future will complete after 1 second (and write the log in the console), but the FutureBuilder will wait until the second Future is completed, and only then show the values from both.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
late Future<String> _futureArticles;
late Future<int> _futureSummary;
#override
void initState() {
_futureArticles = Future.delayed(const Duration(seconds:1), () {print("First is done"); return "First is done";});
_futureSummary = Future.delayed(const Duration(seconds:5), () => 10);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<dynamic>>(
future: Future.wait([_futureArticles, _futureSummary]),
builder: (BuildContext context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.hasData) {
final articles = snapshot.data![0] as String;
final summary= snapshot.data![1] as int;
return Column(children:[
Text(articles),
Text('$summary')
]);
} else {
return const CircularProgressIndicator();
}
});
}
}

Related

Flutter load json data on start

My problem is that, before showing the screen. It should load the necessary data while displaying a splashscreen.
It works fine, until it goes to the create provider, the data which has been loaded into the list is getting cleared due to the list getting recreated. I wonder how can i tackle this? How should i load the data (json) file into the list instead.
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<void> loadJson;
#override
void initState() {
loadJson = QuestionProvider().loadJsonFiles();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: loadJson,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const MaterialApp(home: Splash());
} else {
return MultiProvider(
providers: [
ChangeNotifierProvider<QuestionProvider>(create: (_) => QuestionProvider()),
],
child: MaterialApp(
title: "MyApp",
theme: ThemeData(
primarySwatch: Colors.amber,
),
home: const Home(),
)
);
}
},
);
}
}
class QuestionProvider with ChangeNotifier {
final List<QuestionModel> questionList = <QuestionModel>[];
Future<void> loadJsonFiles() async {
final String response = await rootBundle.loadString("assets/questions.json");
final Map<String, dynamic> data = await jsonDecode(response);
for (int i = 0; i < data.length; i++) {
questionList.add(QuestionModel.fromJson(data[i]));
}
}
}
Why not invert the future builder and the providers?
Widget build(BuildContext context) {
return MultiProvider(
[...],
child: Builder(
builder: (context) =>
FutureBuilder(
future: Provider.of<QuestionProvider>().loadJsonFiles,
child: [...]
),
),
);
}
There may or may not be some disadvantages to this method, specifically, the value of the future is no longer cached, if this worries you, I recommend you cache the value within the QuestionProvider class itself.

How to set home page asynchronously in Flutter?

I am trying to set the home page of the Flutter app asynchronously, but that is not working because the build method cannot have async properties.
class _MyAppState extends State<MyApp> {
// Widget homeWidget;
// #override
// void initState() async {
// super.initState();
// homeWidget = (await AuthUser.getCurrentUser() != null)
// ? NavBarPage()
// : OnBoardingWidget();
// }
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'WizTkt',
theme: Theme.of(context).copyWith(
appBarTheme: Theme.of(context)
.appBarTheme
.copyWith(brightness: Brightness.dark),
primaryColor: Colors.blue),
home: (await AuthUser.getCurrentUser() != null)
? NavBarPage()
: OnBoardingWidget(),
);
}
}
As you can see in the code, I also tried to use initState to set the homepage widget but I cannot make initState an asynchronous function. I feel like there is a better way to choose your homepage in Flutter. What am I missing?
Do note that AuthUser.getCurrentUser() has to be an async function because I use the SharedPreferences library to obtain the login token stored in memory.
You can use FutureBuilder which allows you to build an Widget in a future time.
Here an example:
class OnBoardingWidget extends StatefulWidget {
const OnBoardingWidget({Key key}) : super(key: key);
#override
State<OnBoardingWidget> createState() => _OnBoardingWidgetState();
}
class _OnBoardingWidgetState extends State<OnBoardingWidget> {
final Future<String> _waiter = Future<String>.delayed(
const Duration(seconds: 2), () => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder<String>(
future: _waiter,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
Widget wdgt;
if (snapshot.hasData) {
wdgt = Text('Result: ${snapshot.data}');
} else if (snapshot.hasError) {
wdgt = Text('Ops ops ops');
} else {
wdgt = Text('Not ready yet');
}
return Center(child: wdgt);
},
),
);
}
}

Image.network stops showing images

Sample code to test
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
StreamController controller;
List imgs = [
'https://images.unsplash.com/photo-1519336367661-eba9c1dfa5e9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1050&q=80',
'https://images.unsplash.com/photo-1594164070019-a3bd58576d55?ixlib=rb-1.2.1&auto=format&fit=crop&w=675&q=80',
'http://www.example.com'
];
int i = 0;
#override
void initState() {
super.initState();
controller = StreamController();
const oneSec = const Duration(seconds: 5);
new Timer.periodic(oneSec, (Timer t) {
print('value of i $i');
controller.sink.add(imgs[i]);
i++;
if (i > 2) {
i = 0;
}
});
}
#override
void dispose() {
controller.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: controller.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Image.network(
snapshot.data,
loadingBuilder: (context, child, loading) {
if (loading == null) return Center(child: child);
return Center(child: CircularProgressIndicator());
},
errorBuilder: (context, object, error) {
return Center(child: CircularProgressIndicator());
},
);
} else {
return Container();
}
},
),
);
}
}
The third image is not displayed. It is obvious. But after errorBuilder, The code does not show any other valid network images.
In github, i said it is a bug.
But the team said i must ask the question in stackoverflow
Is it a bug or am i making any mistake?
Please Avoid reading the below texts: *
It looks like your post is mostly code; please add some more details. - Error from stackoverflow. I have to now fill unwanted words without any meaning :-( Sometimes code is enough to describe the problem
I think you can add key: UniqueKey(), inside Image.network.
Flutter always try to reuse most of the widget to avoid rendering cost (maybe it keep the error status). Add UniqueKey to force rebuild.

Using FutureBuilder in main.dart

Below code always show OnboardingScreen a little time (maybe miliseconds), after that display MyHomePage. I am sure that you all understand what i try to do. I am using FutureBuilder to check getString method has data. Whats my fault ? Or any other best way for this ?
saveString() async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('firstOpen', '1');
}
getString() method always return string.
getString() async {
final prefs = await SharedPreferences.getInstance();
String txt = prefs.getString('firstOpen');
return txt;
}
main.dart
home: new FutureBuilder(
future: getString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
})
Usually I'm using another route, rather than FutureBuilder. Because futurebuilder every hot reload will reset the futureBuilder.
There always will be some delay before the data loads, so you need to show something before the data will load.
Snapshot.hasData is showing only the return data of the resolved future.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
const isOnboardingFinished = 'isOnboardingFinished';
class _SplashScreenState extends State<SplashScreen> {
Timer timer;
bool isLoading = true;
#override
void initState() {
_checkIfFirstOpen();
super.initState();
}
Future<void> _checkIfFirstOpen() async {
final prefs = await SharedPreferences.getInstance();
var hasOpened = prefs.getBool(isOnboardingFinished) ?? false;
if (hasOpened) {
_changePage();
} else {
setState(() {
isLoading = false;
});
}
}
_changePage() {
Navigator.of(context).pushReplacement(
// this is route builder without any animation
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return isLoading ? Container() : OnBoarding();
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(child: Text('homePage'));
}
}
class OnBoarding extends StatelessWidget {
Future<void> handleClose(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool(isOnboardingFinished, true);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
onPressed: () => handleClose(context),
child: Text('finish on bording and never show again'),
),
),
);
}
}
From the FutureBuilder class documentation:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So you need to create a new Stateful widget to store this Future's as a State. With this state you can check which page to show. As suggested, you can start the future in the initState method:
class FirstPage extends StatefulWidget {
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final Future<String> storedFuture;
#override
void initState() {
super.initState();
storedFuture = getString();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: storedFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
});
}
}
So in your home property you can call it FirstPage:
home: FirstPage(),
Your mistake was calling getString() from within the build method, which would restart the async call everytime the screen gets rebuilt.

How to refresh or reload a flutter firestore streambuilder manually?

I have this application that needs a pull to refresh functionality, so I placed the StreamBuilder Widget inside the RefreshIndicator Widget, but I don't know how to manually refresh the StreamBuilder when the onRefreshed event is triggered.
Having the stream as a state variable and resetting on pull on refresh will solve the problem.
In below code, I am resetting the stream on button press. Hope that helps you.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
var stream; // state variable
#override
void initState() {
super.initState();
stream = newStream(); // initial stream
}
Stream<String> newStream() =>
Stream.periodic(Duration(seconds: 1), (i) => "$i");
#override
Widget build(BuildContext context) {
var streamBuilder = StreamBuilder(
initialData: "0",
stream: stream,
builder: (context, snapshot) {
return new Text(snapshot.data);
});
return MaterialApp(
title: 'Trial',
home: Scaffold(
appBar: AppBar(title: Text('Stream builder')),
body: Column(
children: <Widget>[
streamBuilder,
FlatButton(
onPressed: () {
setState(() {
stream = newStream(); //refresh/reset the stream
});
},
child: Text("Reset"))
],
)));
}
}
Try to use this sample with rxdart package:
class _MyAppState extends State<MyApp> {
/// Controller for stream with `Object` (actually any) signal
final controller = PublishSubject<Object>();
/// Stream returning as transformed `Future` with Firebase data
Stream<List> get stream => controller.asyncMap(_onRefreshFirebase);
#override
void dispose() {
controller.close();
super.dispose();
}
#override
void initState() {
super.initState();
controller.add(Object()); // Activate stream
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Trial',
home: Scaffold(
appBar: AppBar(title: Text('Stream builder')),
body: Column(
children: <Widget>[
RefreshIndicator(
onRefresh: _onRefresh,
child: StreamBuilder<List>(
stream: stream,
builder: (context, snapshot) {
// use data from `snapshot`
return ListView.builder(...),
},
),
),
FlatButton(
child: Text('Reload'),
onPressed: () {
// Prepare new data and send it to stream
controller.add(Object());
}
),
],
),
);
}
}
}
/// Stream transformer from `event` to `List<dynamic>`
Future<List> _onRefreshFirebase(Object event) async {
// get data from Firebase
return [];
}
/// Callback when user pulls down
Future _onRefresh() async {
await _onRefreshFirebase(Object());
}