I'm trying to implement local database support in my flutter app which is being managed using Provider, now I want to make the retrieving of data obey the state management pattern, but I've been failing to.
I've tried to make a traditional Provider to achieve this but the app got stuck in a loop of requests to the database, so after some search I found the FutureProvider, but I cant find how can I get a snapshot from the data being loaded
class _ReceiptsRouteState extends State<ReceiptsRoute> {
List<Receipt> receipts = [];
#override
Widget build(BuildContext context) {
return FutureProvider(
initialData: List(),
builder: (_){
return DBProvider().receipts().then((result) {
receipts = result;
});
},
child: Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context).history),
),
body: Container(
child: ListView.builder(
itemBuilder: (context, position) {
final item = receipts[position];
return ListTile(
title: Text(item.date),
);
},
),
),
),
);
}
}
now my app is running as I want but not as how it should run, I used FutureBuilder to get the data from the database directly but I know it should come through the provider so I want to make it right
FutureProvider exposes the result of the Future returned by builder to its descendants.
As such, using the following FutureProvider:
FutureProvider<int>(
initialData: 0,
builder: (_) => Future.value(42),
child: ...
)
it is possible to obtain the current value through:
Provider.of<int>(context)
or:
Consumer<int>(
builder: (context, value, __) {
return Text(value.toString());
}
);
In my example I used the create parameter of FutureProvider to request the API, then then I used Consumer to get the results of the API.
FutureProvider(
create: (_) => peopleService.getAllSurvivor(),
child: Consumer<List<Survivor>>(builder: (context, survivors, _) {
return survivors == null
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: survivors.length,
itemBuilder: (context, index) {
var survivor = survivors[index];
return ListTile(
title: Text(survivor.name),
subtitle: Text(survivor.gender),
leading: Icon(Icons.perm_identity),
);
},
);
})));
Related
I got trouble with firebase fireStore.
There is a stream builder reading data from items collection.
Inside items collection there is some fields and another collections.
I haven't any problem with fields, the problem is with collection.
how to access those collections inside stream builder?
StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: CallApi().finalReference(reference: widget.finalReference),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text('snapshot Error:${snapshot.error}'));
}
if (snapshot.hasData) {
var snapData = snapshot.data!.docs;
if (kDebugMode) {
print(snapData.length);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.builder(
itemCount: snapData.length,
itemBuilder: (BuildContext context, int index) {
return ListItem(
mTitle: snapData[index].get('title') ?? '',
mSubTitle: snapData[index].get('address') ?? 'empty',
mPrice: snapData[index].get('price') ?? '',
mImageUrl: snapData[index].get('gallery')[0],
mOnTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsPage(
adsTitle: snapData[index].get('title'),
adsSubTitle: snapData[index].get('subTitle'),
gallery: snapData[index].get('gallery'),
specFTitle: snapData[index].get('gallery'),
),
),
);
},
);
},
),
),
],
);
}
return const Center(child: CircularProgressIndicator());
},
),
here is firebase
Reading data from Firestore is a shallow operation. When you read a document, its subcollection are not automatically read.
So if you want to get the data from the subcollections of the current document, you will have to start a new read operation for that. If you want to show that data in the UI, you can use a new, nested StreamBuilder or FutureBuilder for that.
I am working on app to import a lit from google drive in the form of JSON file, the App will read the JSON file only, the reason is I am not using Firestore Database is because it delays the build of the App & whatever I tried still I have errors & I can't build the APP on IOS device or simulator, so every time I will try to import the data the App will show error. my code is as the following
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Samer'),
),
body: FutureBuilder<List<User>>(
future: UsersApi.getApps(),
builder: (context, snapshot) {
final users = snapshot.data;
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
return const Center(child: Text('error'));
} else {
return buildUsers(users!);
}
}
},
),
);
}
Widget buildUsers(List<User> users) => ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: users.length,
itemBuilder: (BuildContext context, int index) {
final user = users[index];
return Padding(
padding: const EdgeInsets.all(15.0),
child: Container(
color: kOurAppsMainScreenCards,
child: ListTile(
title: Text(user.appName),
// leading: CircleAvatar(
// backgroundImage: NetworkImage(user.imageName),
// ),
// subtitle: Platform.isIOS
// ? Text(user.paidFreeIOS)
// : Text(user.paidFree),
),
),
);
},
);
}
and I am using http package as following:
class UsersApi {
static Future<List<User>> getApps() async {
final url = Uri.parse(
'https://drive.google.com/file/d/1tAxO2kRD0NVBhefA3srbj1SKQ2l8u9Wc/view?usp=sharing');
final response = await http.get(url);
final body = json.decode(response.body);
return body.map<User>(User.fromJson).toList();
}
}
the stage thing is I inserted the same JSON file in firestore storage and the App read it....
Can somebody please help me.
Regards,
I finally found a solution to this, the main issue is in the link itself, we have to do some modification to the link & then we can use it freely, for example:
if this is the file link, we will use the bold lines in the modified link
https://drive.google.com/file/d/1-T9OTLTZXTB7ydqV3tcgG4T2FQckMooB/view?usp=sharing
and we have to add few words to make the link workable 100%,
the following code to be added
uc?export=view&id=
the new link will be
https://drive.google.com/uc?export=view&id=1-T9OTLTZXTB7ydqV3tcgG4T2FQckMooB
Solution ref (it was for photos but it worked in JSON files also)
https://youtu.be/0ZHqrf0mzrI
I am trying to create a "CategoryStream" to update the UI based on the users choice.
This is my stream:
import 'dart:async';
class CategoryStream {
StreamController<String> _categoryStreamController =
StreamController<String>();
closeStream() {
_categoryStreamController.close();
}
updateCategory(String category) {
print("Update category in stream was called");
print("the new category is: " + category);
_categoryStreamController.sink.add(category);
}
Stream<String> get stream => _categoryStreamController.stream;
}
And my StreamBuilder looks like this:
return StreamBuilder<String>(
stream: CategoryStream().stream,
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data.toString()),
),
);
},
);
So when the User choses a new category, i try to update the Stream like this:
CategoryStream().updateCategory(currentChosenCategory);
Whatever i do, the result is always null. Although the right category is displayed in the print() function...
What am i missing?
Maybe i need to work with a StreamProvider? Not a StreamBuilder? Because i am adding the data from a Parent-Widget to a Child-Widget of a Child-Widget..
By default, the value of a StreamController is null. You need to set at initialData or add data to the stream before you call snapshot.data in your StreamBuilder:
final CategoryStream _categoryStream = CategoryStream();
return StreamBuilder<String>(
stream: _categoryStream.stream,
initialData: "",
builder: (context, snapshot) {
return Container(
color: Colors.white,
child: Center(
child: Text(snapshot.data), //toString() is unnecessary, your data is already a string
),
);
},
);
I have a basic app, loading data into a list view widget, I have a 2nd set of data I'd like to be able to reference
This kind of thing, but in the creation of the List Tile, I want to use data from another method called getOtherData() .. essentially to join the data but I'd rather not do it in sql/object creation..
child: FutureBuilder<List<Contact>>(
future: getContacts(),
builder: (BuildContext context,
AsyncSnapshot<List<Contact>> snapshot) {
return ListView(
children: snapshot.hasData
? snapshot.data
.map((e) =>[ ListTile(
leading: ExcludeSemantics(
child: CircleAvatar(
child: Text(e.daysSinceContacted
.toString()),
)),
title: Text(e.firstName),
subtitle: Text(DateTime.now()
.difference(e.lastContacted)
.inDays
.toString() +
" Days" +
":" +
e.lastContacted
.toIso8601String() +
getGroupName(e, groups)
.whenComplete((x) => x)),
enabled: true,
},
))))
.toList()
: []);
})),
So where I do
title: Text(e.firstName),
I'd like to do
title: Text(e.firstName + getOtherData(e.id)
I can't figure out how to deal with the Future<> returns from the db sync methods though...
basically, just how do I get data so that I can use it?
The easiest way might be to use another FutureBuilder.
title: FutureBuilder<String>(
future: getOtherData(e.id),
builder: (_, snapshot) {
if(!snapshot.hasData) return Container(); // or return something else while loading
final otherData = snapshot.data;
return Text(e.firstName + otherData);
},
),
I'm right now trying to make a Quiz like application in flutter for learning this Framework.
I've implemented Firebase Firestore to manage the application data.
From this flutter plugin documentations I read about binding a CollectionReference to a ListView and it was pretty easy.
My problem is the following.
I've got some categories to display in the Home page, I want the user to be able to select which one he wants and then store this information in a List.
With this code I can display the list:
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('Categorie').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Center(
child: Column(
children: <Widget>[
SizedBox(
child: CircularProgressIndicator(),
height: 50.0,
width: 50.0,
),
Text('Dowload delle categorie...')
],
),
);
default:
_loadListOfCategories(snapshot);
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
var nome = document['Nome'];
print('doc: $nome');
return new CategoryWidget(
id: document.documentID,
tap: onCategoryTap,
nome: document['Nome'],
count: document['Count'],
checked: false,
);
}).toList(),
);
}
},
);
}
CategoryWidget is just a simple stateless widget which act as a ListTile.
The result is the following:
Now, how can I save a List full of Category models, which one implementing a "checked/unchecked" property, and how can I keep this List updated?
I tried using "_loadListOfCategories()" method exacly inside the builder:
void _loadListOfCategories(AsyncSnapshot<QuerySnapshot> snapshot) {
var temporalList = new List<CategoryModel>();
for (DocumentSnapshot doc in snapshot.data.documents) {
temporalList.add(new CategoryModel(
id: doc.documentID,
count: doc['Count'],
nome: doc['Nome']));
}
setState(() {
_listOfCategories = temporalList;
});
}
But I couldn't call setState() here becouse I'm actually inside the building method.
use a map, your key will be 'document.documentID' and the value a boolean.
Map map = Map() // assuming this a string document.documentID
checked: map.get(document.documentID),
in your checkbox you call
setState()=>
map.put(document.documentID, !map.get(document.documentID));