Circular loading before page loads Flutter - flutter

I have a list of widgets and a function which returns a Container telling user to add to the list if the list of widgets is empty:
_isCountEmpty() {
if (count == 0 || count == null) {
//if no widgets in list
return Container(
color: Colors.black,
child: Text('Press edit to start adding exercises',
style: TextStyle(
fontSize: 40,
color: Colors.white,
)));
}
//if widgets in list
return ListView(
children: children,
scrollDirection: Axis.vertical,
);
}
initState where count is obtained:
void initState() {
getCount().then(updateCount);
super.initState(); }
The count value is received from another page using shared preferences which are called in my initState. Before the count value is received in the initState the screen thinks the value for count is null therefore for just a split second the Container() is returned just before the ListView.
Instead of this, I want the circular indicator to show while the screen is getting the value of count from initState, and then return the Container or ListView after the value for count is obtained
Thank you

You can try the following code which would load your counter from shared preferences then you can replace the Text widget with whatever list you want or container if the counter == 0 || counter == null
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(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int counter;
Future<SharedPreferences> prefs;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder(
future: prefs,
builder: (context, AsyncSnapshot<SharedPreferences> snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
counter = snapshot.data.getInt("counter");
//Replace with whatever widget you want
return Text("Loading is done your counter is : $counter");
}
},
)),
);
}
#override
void initState() {
super.initState();
prefs = SharedPreferences.getInstance();
}
}

Related

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

Flutter FutureProvider Execute build twice

I've changed from Statefulwidget using initState to fetch the data and Futurebuilder to load it to Futureprovider. But it seems like Futureprovider is execute build method twice, while my previous approach executed it once. Is this behaviour normal?
class ReportsPage extends StatelessWidget {
const ReportsPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureProvider<List<ReportModel>>(
create: (_) async => ReportsProvider().loadReportData(1),
initialData: null,
catchError: (_, __) => null,
child: const ReportWidg()
);
}
}
class ReportWidg extends StatelessWidget {
const ReportWidg();
#override
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
if (reportList == null) {
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Det finns inga rapporter."));
}
print(reportList.length);
return Container();
}
}
Im relativly new to flutter but I think its because StatelessWidget is #immutable, which means whenever something changes it needs to rebuild itself.
At first build there is async calling made and ReportWidg() is rendered.
Then this line final reportList = Provider.of<List<ReportModel>>(context); get new fetched data as result of async function therefore immutable widget needs to rebuild itself because it cannot be "changed".
In object-oriented and functional programming, an immutable object
(unchangeable object) is an object whose state cannot be modified
after it is created. ... This is in contrast to a mutable object
(changeable object), which can be modified after it is created
or am I wrong ?
I suspect your FutureProvider should be hoisted to a single instantiation, like placed into a global variable outside any build() methods. This will of course cache the result, so you can set it up for rebuild by having the value depend on other Providers being watch()ed or via FutureProvider.family.
You can copy paste run full code below
Yes. it's normal
First time Execute Build reportList is null and show CircularProgressIndicator()
Second time Execute Build reportList has data and show data
If you set listen: false , final reportList = Provider.of<List<ReportModel>>(context, listen: false);
You get only one Execute Build and the screen will always show CircularProgressIndicator()
In working demo simulate 5 seconds network delay so you can see CircularProgressIndicator() then show ListView
You can reference https://codetober.com/flutter-provider-examples/
code snippet
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
print("reportList ${reportList.toString()}");
if (reportList == null) {
print("reportList is null");
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Empty"));
}
return Scaffold(
body: ListView.builder(
itemCount: reportList.length,
working demo
full code
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ReportModel {
String title;
ReportModel({this.title});
}
class ReportsProvider with ChangeNotifier {
Future<List<ReportModel>> loadReportData(int no) async {
await Future.delayed(Duration(seconds: 5), () {});
return Future.value([
ReportModel(title: "1"),
ReportModel(title: "2"),
ReportModel(title: "3")
]);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ReportsPage(),
);
}
}
class ReportsPage extends StatelessWidget {
const ReportsPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureProvider<List<ReportModel>>(
create: (_) async => ReportsProvider().loadReportData(1),
initialData: null,
catchError: (_, __) => null,
child: const ReportWidg());
}
}
class ReportWidg extends StatelessWidget {
const ReportWidg();
#override
Widget build(BuildContext context) {
print("Execute Build");
final reportList = Provider.of<List<ReportModel>>(context);
print("reportList ${reportList.toString()}");
if (reportList == null) {
print("reportList is null");
return Center(child: CircularProgressIndicator());
} else if (reportList.isEmpty) {
return Center(child: Text("Empty"));
}
return Scaffold(
body: ListView.builder(
itemCount: reportList.length,
itemBuilder: (context, index) {
return Card(
elevation: 6.0,
child: Padding(
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, left: 8.0, right: 8.0),
child: Text(reportList[index].title.toString()),
));
}),
);
}
}
In your case you should use Consumer, i.e.
FutureProvider<List<ReportModel>(
create: (_) => ...,
child: Consumer<List<ReportModel>(
builder: (_, reportList, __) {
return reportList == null ?
CircularProgressIndicator() :
ReportWidg(reportList);
}
),
),
But in this case you must to refactor your ReportWidg.

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.

StreamBuilder not re-rendering the widget inside?

I created this code, what i want to happen is when i press on the button i want the piechart to re-render with the new values (which should be old values but the food value increased by 1)
I am using a piechart from pie_chart: 0.8.0 package.
Deposit is nothing but a pojo (String category and int deposit)
the bloc.dart contains a global instance of the bloc, a getter for the stream and initialization of a stream of type
Here's my code:
import 'package:flutter/material.dart';
import 'package:pie_chart/pie_chart.dart';
import 'bloc.dart';
import 'Deposit.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'bloc Chart',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
Map<String, double> datamap = new Map();
#override
Widget build(BuildContext context) {
datamap.putIfAbsent("Food", () => 5);
datamap.putIfAbsent("transportation", () => 3);
return Scaffold(
appBar: AppBar(
title: Text("PieChart using blocs"),
),
body: Column(
children: <Widget>[
StreamBuilder<Deposit>(
stream: bloc.data, //A stream of Deposit data
builder: (context, snapshot) {
addDeposit(Deposit("Food", 1), datamap);
debugPrint("Value of food in map is: ${datamap["Food"]}");
return PieChart(dataMap: datamap);
}),
SizedBox.fromSize(
size: Size(20, 10),
),
RaisedButton(
onPressed: () {
bloc.add(Deposit("Food", 1)); //returns the stream.add
},
child: Icon(Icons.add),
),
],
),
);
}
void addDeposit(Deposit dep, Map<String, double> map) {
if (map.containsKey(dep.category)) {
map.update(dep.category, (value) => value + dep.price);
} else
map.putIfAbsent(dep.category, () => dep.price);
}
}
I think your problem is that the stream doesn't trigger new events. You don't have to close the stream to rebuild. I can't see anywhere in your code where you are triggering new events for the stream. Check below code to see a simple way how you can update a StatelessWidget using a StreamBuilder.
class CustomWidgetWithStream extends StatelessWidget {
final CustomBlock block = CustomBlock();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder(
stream: block.stream,
builder: (context, stream) {
return Text("${stream.data.toString()}");
}),
RaisedButton(
onPressed: () {
block.incrementNumber();
},
child: Text("Increment"),
)
],
);
}
}
class CustomBlock {
num counter = 10;
final StreamController<num> _controller = StreamController();
Stream<num> get stream => _controller.stream;
CustomBlock() {
_controller.onListen = () {
_controller.add(counter); // triggered when the first subscriber is added
};
}
void incrementNumber() {
counter += 1;
_controller.add(counter); // ADD NEW EVENT TO THE STREAM
}
dispose() {
_controller.close();
}
}
Although this is a working code snippet, I would strongly suggest to change your widget from StatelessWidget to StatefulWidget, for two reasons:
* if you go "by the book", if a widget changes the content by itself, then it's not a StatelessWidget, a stateless widget only displays data that is given to it. In your case, the widget is handling the tap and then decides what to do next and how to update itself.
* if you are using streams, in a stateful widget you can safely close the stream, as you can see in the above code, there's no safe way to close the stream. If you don't close the stream, there might be unwanted behaviour or even crashes.
This is my bloc file
import 'package:rxdart/rxdart.dart';
import 'package:testing/Deposit.dart';
class Bloc{
final _data = new BehaviorSubject<Deposit>();
Stream<Deposit> get data => _data.stream;
Function(Deposit) get add => _data.sink.add;
void dispose(){
_data.close();
}
}
Bloc bloc = new Bloc();

how to keep the state of my widgets after scrolling?

I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}