How to get data from server with provider architecture - flutter

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

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

how to await for network connectivity status in flutter

I have used connectivity_plus and internet_connection_checker packages to check the internet connectivity.
The problem occured is , the app works perfectly fine as expected when the app start's with internet on state. But when the app is opened with internet off, the dialog isn't shown !!
I assume this is happening because the build method is called before the stream of internet is listened.
Code :
class _HomePageState extends State<HomePage> {
late StreamSubscription subscription;
bool isDeviceConnected = false;
bool isAlertSet = false;
#override
void initState() {
getConnectivity();
super.initState();
}
getConnectivity() {
subscription = Connectivity().onConnectivityChanged.listen(
(ConnectivityResult result) async {
isDeviceConnected = await InternetConnectionChecker().hasConnection;
if (!isDeviceConnected && isAlertSet == false) {
showDialogBox();
setState(() {
isAlertSet = true;
});
}
},
);
}
#override
void dispose() {
subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
);
}
showDialogBox() => showDialog(/* no internet dialog */)
Extending the question: Is it assured that this works for all the pages ?
if yes, how ?
if not , how to overcome this?
First of all you need to listen for internet connectivity in your app first screen which is probably app.dart
GlobalKey<NavigatorState> navigatorKey = GlobalKey();
final noInternet = NoInternetDialog();
class TestApp extends StatefulWidget {
#override
State<TestApp> createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
void initState() {
super.initState();
checkInternetConnectivity();
}
#override
Widget build(BuildContext context) {
return MaterialApp(...);
}
Future<void> checkInternetConnectivity() async {
Connectivity().onConnectivityChanged.getInternetStatus().listen((event)
{
if (event == InternetConnectionStatus.disconnected) {
if (!noInternet.isShowing) {
noInternet.showNoInternet();
}
}
});
}
}
Make the screen stateful in which you are calling MaterialApp and in initState of that class check for your internet connection, like above
You are saying how can I show dialog when internet connection changes for that you have to create a Generic class or extension which you can on connectivity change. You have to pass context to that dialogue using NavigatorKey
class NoInternetDialog {
bool _isShowing = false;
NoInternetDialog();
void dismiss() {
navigatorKey.currentState?.pop();
}
bool get isShowing => _isShowing;
set setIsShowing(bool value) {
_isShowing = value;
}
Future showNoInternet() {
return showDialog(
context: navigatorKey.currentState!.overlay!.context,
barrierDismissible: true,
barrierColor: Colors.white.withOpacity(0),
builder: (ctx) {
setIsShowing = true;
return AlertDialog(
elevation: 0,
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.all(3.0.h),
content: Container(...),
);
},
);
}
}
Use checkConnectivity to check current status. Only changes are exposed to the stream.
final connectivityResult = await Connectivity().checkConnectivity();

How to paginate using Future Builder widget and flutter firestore?

import 'package:flutter/cupertino.dart';
class FarmplaceScreen extends StatefulWidget {
const FarmplaceScreen({Key key}) : super(key: key);
#override
_FarmplaceScreenState createState() => _FarmplaceScreenState();
}
class _FarmplaceScreenState extends State<FarmplaceScreen>
with AutomaticKeepAliveClientMixin {
final _nativeAdController = NativeAdmobController();
int limit = 15;
DocumentSnapshot lastVisible;
bool _hasNext = true;
bool _isFetching = false;
bool needMore = false;
final List<DocumentSnapshot> allProducts = [];
var productFuture;
var _getProductFuture;
ScrollController _scrollController = new ScrollController();
#override
void initState() {
super.initState();
if(lastVisible == null) productFuture =getUsers();
_scrollController.addListener(() {
if(_scrollController.offset >= _scrollController.position.maxScrollExtent){
if(_hasNext){
productFuture = getUsers();
setState(() {
_isFetching = true;
});
}
}
});
}
Future <QuerySnapshot> getUsers() {
if(_isFetching) return Future.value();
final refUsers = FirebaseFirestore.instance.collection('product').orderBy('publishedDate').limit(15);
Future.value(refUsers.startAfterDocument(allProducts.last).get());
if(lastVisible == null){
return Future.value(refUsers.get());
}
else{
return Future.value(refUsers.startAfterDocument(lastVisible).get());
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
child: FutureBuilder<QuerySnapshot>(
future: productFuture,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return ErrorDisplay();
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Container(
child: Center(child: circularProgress()),
);
}
lastVisible = snapshot.data.docs[snapshot.data.docs.length-1];
if (snapshot.data.docs.length < 15){
_hasNext = false;
}
if (snapshot.connectionState == ConnectionState.waiting){
_isFetching = true;
}
if (snapshot.connectionState == ConnectionState.done){
_isFetching = false;
}
allProducts.addAll(snapshot.data.docs);
return new GridView.countBuilder();
},
)
);
}
}
#override
bool get wantKeepAlive => true;
}
Hello Folks,
I am trying to achieve pagination using flutter Future builder widget.
Situation:
I am able to load first 15 products using the method stated above.
The problem occurs when I try to load the next 15 products.
I get the next next 15 products in line but, the future builder widget rebuilds. Now, to avoid the rebuild I have tried to initialize the future (productFuture) in the initState, but it dosen't solve the problem.
I tried setting _getProductFuture = productFuture in the initstate and then using _getProductFuture as the future in the FutureBuilder widget. In this case the widget doesn't rebuild but, the first 15 products are repeated everytime I scroll to the bottom of the screen.
Please suggest how I can stop this unnecessary rebuild of the FutureBuilder widget.
FYI: AbdulRahmanAlHamali's solution on GitHub dosen't work in this case.

How to display loading widget until the main widget is loaded in 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()

Accessing Flutter context when creating StatefulWidget

I'm having trouble accessing a services object when initializing a stateful widget. The problem comes from the context object not being available in initState.
I'm using InheritedWidget to inject a services object in my main.dart file like so
void main() async {
final sqflite.Database database = await _openDatabase('db.sqlite3');
runApp(
Services(
database: database,
child: MyApp(),
),
);
}
The Services object is quite straightforward. It will have more than just the database as a member. The idea is that the widgets don't need to know if a local database, local cache, or remote server is being accessed.
class Services extends InheritedWidget {
final Database database;
const Services({
Key key,
#required Widget child,
#required this.database,
}) : assert(child != null),
assert(database != null),
super(key: key, child: child);
Future<List<models.Animal>> readAnimals() async {
return db.readAnimals(database: this.database);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
static Services of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Services) as Services;
}
}
The trouble comes in my _HomePageState state when I want to load all the animals from the database. I need to access the Services object. I cannot access the Services object in initState so I am using didChangeDependencies. A problem comes when the home page is removed from the stack. It seems didChangeDependences is called and the access to the context object is illegal. So I created an _initialized flag that I can use in didChangeDependencies to ensure I only load the animals the first time. This seems very inelegant. Is there a better way?
class _HomePageState extends State<HomePage> {
bool _initialized = false;
bool _loading = false;
List<Animal> _animals;
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.of(this.context).appName),
),
body: _HomeBody(
loading: this._loading,
animals: this._animals,
),
);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (!this._initialized) {
this._initialized = true;
this._loadAnimals();
}
}
void _loadAnimals() async {
this.setState(() {
this._loading = true;
this._animals = null;
});
final List<Animal> animals = await Services.of(this.context).readAnimals();
this.setState(() {
this._loading = false;
this._animals = animals;
});
}
}
For that case you could use addPostFrameCallback of your WidgetsBinding instance to execute some code after your widget was built.
_onLayoutDone(_) {
this._loadAnimals();
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}