How to display loading widget until the main widget is loaded in flutter? - flutter

I have a widget with a lot of contents like image, text and more, which make it heavy widget in flutter app, But when the app is navigated to the widget having the complex widget the app faces the jank since the widget is too large to load at an instant,
I want to show simple lite loading widget until the original widget is loaded thus removing the jank from the app and enable lazy loading of the widget,
How to achieve this in flutter?
EDIT:-
To make it clear, I am not loading any data from the Internet, and this is not causing the delay. For Loading the data from Internet we have FutureBuilder. Here my widget is itself heavy such that it takes some time to load.
How to display loading Widget while the main widget is being loaded.

First you have to create a variable to keep the state
bool isLoading = true; //this can be declared outside the class
then you can return the loading widget or any other widget according to this variable
return isLoading ?
CircularProgressIndicator() //loading widget goes here
: Scaffold() //otherwidget goes here
you can change between these two states using setState method
Once your data is loaded use the below code
setState(() {
isLoading = false;
});
Sample Code
class SampleClass extends StatefulWidget {
SampleClass({Key key}) : super(key: key);
#override
_SampleClassState createState() => _SampleClassState();
}
bool isLoading = true; // variable to check state
class _SampleClassState extends State<SampleClass> {
loadData() {
//somecode to load data
setState(() {
isLoading = false;//setting state to false after data loaded
});
}
#override
void initState() {
loadData(); //call load data on start
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
child: isLoading ? //check loadind status
CircularProgressIndicator() //if true
:Container(), //if false
);
}
}

This is a perfect place to use a FutureBuilder.
Widget loadingWidget = ...;
Future<Widget> buildHeavyWidget() async {
// build and return heavy widget
}
FutureBuilder(
future: buildHeavyWidget(),
builder: (context, snapshot) {
if(snapshot.hasData) {
// after the future is completed
// the heavy widget is availabe as snapshot.data
return snapshot.data;
}
return loadingWidget;
},
)

First define a bool value.
bool isLoading = false;
In your function.
yourfunction(){
setState(){
isLoading = true;
}
setState(){
isLoading = false;
}
}
In your widget.
isLoading?CircularProgressIndicator():Widget()

Related

How to wait until datas are loaded?

I try to learn flutter and i face an issue with data loading.
I get information from sqlite database to display them in my homepage.
When starting my app, i have an error :
LateInitializationError: Field 'child' has not been initialized.
late MonneyServices monneyServices;
late ChildDAO childDAO;
late Child child;
void initState() {
super.initState();
this.monneyServices = MonneyServices();
monneyServices.getChild().then((Child child) {
this.child = child;
setState(() {});
});
the getChild method is async
Future<Child> getChild() async {
//return Child(1, 'Alice2', 100);
Child child = Child(1, 'A', 1);
this.childDAO.insertChild(Child(1, "Alice", 10));
List<Child> childList = await this.childDAO.getChilds();
child = childList.first;
print(childList.first);
return child;
}
I use so datas in
#override
Widget build(BuildContext context)
How can i wait until datas are loaded ?
Thanks for your help
You could use FutureBuilder.
It lets you to await for a future to complete and return a different widget according to the future status.
In your case you should use it in the build method and not in initState.
You should use it more or less like so:
Widget build(BuildContext context) {
return FutureBuilder<Widget>(context, snapshot){
if(snapshot.hasData){ //If the future has completed
return snapshot.data; //You return the widget it completed to
} else {
return CircularProgressIndicator(); //Otherwise, return a progress indicator
}
}
}
you can use a boolean variable be sure the data is loaded and reflect this in the build
late MonneyServices monneyServices;
late ChildDAO childDAO;
late Child child;
bool isLoading = true; // <--
void initState() {
super.initState();
this.monneyServices = MonneyServices();
monneyServices.getChild().then((Child child) {
this.child = child;
isLoading = false; // <--
setState(() {});
});
and in the build:
#override
Widget build(BuildContext context) {
if(isLoading) {
return Text('loading...');
}
return child;
}

Flutter Getx builder not updating UI

I'm trying to use a GetX Builder in combination with a bool in the controller to display a loading spinner while fetching data and then the data afterwards.
Printing the update to the bool shows it finishes loading and changes the variable but the UI is never updated to reflect this.
Controller
class AuthController extends GetxController {
//final RxBool isLoading = true.obs;
//var isLoading = true.obs;
final Rx<bool> isLoading = Rx<bool>(true);
setLoading(bool status) {
isLoading.value = status;
print(isLoading.value);
update();
}
fetchUserData() async {
setLoading(true);
_firebaseUser.bindStream(_auth.authStateChanges());
if (_auth.currentUser != null) {
bool profileRetrieved =
await FirestoreDB().getUserProfile(_auth.currentUser!.uid).then((_) {
return true;
});
setLoading(!profileRetrieved);
}
}
}
Profile Page
class ProfileCard extends StatelessWidget {
const ProfileCard({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder<AuthController>(
init: AuthController(),
initState: (_) => AuthController().fetchUserData(),
builder: (controller) {
if (controller.isLoading.value) {
return const Center(child: CircularProgressIndicator());
}
return Container(...);
},
);
}
}
That widget is called within another as part of the wider UI. Let me know if you'd need to see that and/or anything else.
As you can see I've tried different ways of setting up the bool and none of them seem to trigger a change of UI in the builder.
Probably doing something stupid but I've tried a few different approaches and looked around for people having similar problems but not been able to crack it.
Thanks in advance.
You are using isLoading as a Rx<bool>. In that case you need to change GetBuilder into GetX. And no need to call update().
GetBuilder is for non-reactive, non-Rx, simple state management
GetX is for reactive, Rx state management

Flutter: How to share an instance of statefull widget?

I have a "WidgetBackGround" statefullwidget that return an animated background for my app,
I use it like this :
Scaffold( resizeToAvoidBottomInset: false, body: WidgetBackGround( child: Container(),),)
The problem is when I use navigator to change screen and reuse WidgetBackGround an other instance is created and the animation is not a the same state that previous screen.
I want to have the same animated background on all my app, is it possible to instance it one time and then just reuse it ?
WidgetBackGround.dart look like this:
final Widget child;
WidgetBackGround({this.child = const SizedBox.expand()});
#override
_WidgetBackGroundState createState() => _WidgetBackGroundState();
}
class _WidgetBackGroundState extends State<WidgetBackGround> {
double iter = 0.0;
#override
void initState() {
Future.delayed(Duration(seconds: 1)).then((value) async {
for (int i = 0; i < 2000000; i++) {
setState(() {
iter = iter + 0.000001;
});
await Future.delayed(Duration(milliseconds: 50));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return CustomPaint(painter: SpaceBackGround(iter), child: widget.child);
}
}
this is not a solution, but maybe a valid workaround:
try making the iter a static variable,
this of course won't preserve the state of WidgetBackGround but will let the animation continue from its last value in the previous screen
A valid solution (not sure if it's the best out there):
is to use some dependency injection tool (for example get_it) and provide your WidgetBackGround object as a singleton for every scaffold in your app

How to get data from server with provider architecture

I am new to flutter and recently I watch some videos about state management and provider.
I used statefulwidget and in it's initstate I fetched data.
Now is it possible to fetch data in stateless one and manage data in our provider class?
Thanks.
Any help is appreciated.
Yes you can fetch data from stateless widget
Example:-
class RecipeProvider with ChangeNotifier {
bool isLoading = false;
void fetchAnyData(BuildContext context) {
//your data fetching logic
isLoading = true;
ApiManager.downloadRecipeApi().then((recipeList) {
this.recipes = recipeList;
isLoading = false;
notifyListeners();
print("===Success $recipeList");
}).catchError((onError) {
isLoading = false;
notifyListeners();
print("===onError $onError");
Toast.show(onError.errorMsg, context, duration: 2);
});
}
}
ProviderFetchWidget.dart
class ProviderFetchWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final _provider = Provider.of<RecipeProvider>(context);
_provider.fetchAnyData(context);
return Scaffold(
body: provider == null || provider.isLoading
? Center(child: CircularProgressIndicator())
: Center(child: Text("data fetching done")),
);
}

Canonical way to use FutureBuilder with NetworkImage

I would like to be able to get to a network image within a single microtask if the image is already loaded. However, with the current API available in NetworkImage and FutureBuilder, this does not seem to be possible.
This is how we typically wire the two:
NetworkImage imageProvider = getSomeNetworkImage(id);
Completer<ui.Image> completer = Completer<ui.Image>();
imageProvider.resolve(ImageConfiguration()).addListener(
(ImageInfo info, _) => completer.complete(info.image));
return FutureBuilder<ui.Image>(
future: completer.future,
builder: (BuildContext futureBuilderContext, AsyncSnapshot<ui.Image> snapshot) {
if (!snapshot.hasData) {
return _buildPlaceholder();
} else {
return _buildActual(context, snapshot.data, imageProvider);
}
},
);
addListener() immediately calls completer.complete() if the image is already there. However, FutureBuilder is based off of completer.future which does not complete until the next microtask. So even when the image is available, placeholder is displayed momentarily.
What is the best way to avoid this? Perhaps, imageProvider should expose a Future that prevents us from piping this through a completer?
Instead of using a FutureBuilder, I would take advantage of the syncCall argument passed to the listener of the ImageStream. This will tell you if the image resolved immediately, meaning it is already cached. Otherwise you can call setState and trigger a rebuild when it does complete.
class Example extends StatefulWidget {
const Example({Key key, this.image, this.child}): super(key: key);
final ImageProvider image;
final Widget child;
#override
State createState() => new ExampleState();
}
class ExampleState extends State<Example> {
bool _isImageLoaded = false;
#override
void initState() {
widget.image
.resolve(const ImageConfiguration)
.addListener(_handleResolve);
super.initState();
}
#override
Widget build(BuildContext context) {
// if syncCall = true, then _handleResolve will have already been called.
if (_isImageLoaded)
return new Image(widget.image);
return widget.child;
}
void _handleResolve(ImageInfo info, bool syncCall) {
_isImageLoaded = true;
if (!syncCall) {
// we didn't finished loading immediately, call setState to trigger frame
setState(() { });
}
}
}