I would like to combine built_value with cloud_firestore in my Flutter project. I believe it makes sense and that is how it should do it in a clean world? (see https://firebase.flutter.dev/docs/firestore/usage/#typing-collectionreference-and-documentreference).
This is where I am so far.
The Place model:
// ../models/place.dart
import 'dart:convert';
import 'package:built_collection/built_collection.dart';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:sitback/models/serializers.dart';
part 'place.g.dart';
abstract class Place implements Built<Place, PlaceBuilder> {
// Fields
String? get id;
String get name;
String get address;
BuiltList<String> get methods;
Place._();
factory Place([void Function(PlaceBuilder) updates]) = _$Place;
String toJson() {
return json
.encode(standardSerializers.serializeWith(Place.serializer, this));
}
// https://github.com/google/built_value.dart/issues/964#issuecomment-850419921
static Place? fromJson(String jsonString) {
return standardSerializers.deserializeWith(
Place.serializer, json.decode(jsonString));
}
Map<String, Object?> toFirestore() {
return serializers.serializeWith(Place.serializer, this);
}
// TODO: check if something can be improved
// https://github.com/google/built_value.dart/issues/964#issuecomment-850419921
static Place? fromFirestore(Map<String, dynamic> json) {
return serializers.deserializeWith(Place.serializer, json);
}
static Serializer<Place> get serializer => _$placeSerializer;
}
In the above code, I have two issues:
A value of type 'Object?' can't be returned from the method 'toFirestore' because it has a return type of 'Map<String, Object?>'
However, this is a solution that is proposed at multiple locartions (SO questions, github issues), so I don't understand why I get that error.
In the same reference tutorials/videos, they use Place instead of Place?, as a return value for fromJson but then I have the following error:
A value of type 'Place?' can't be returned from the method 'fromJson' because it has a return type of 'Place'
Could it be related to the null-safety recent changes?
Below are the serializers (following the doc/tutorials):
// ../models/serializers.dart
library serializers;
import 'package:built_collection/built_collection.dart';
import 'package:built_value/json_object.dart';
import 'package:built_value/serializer.dart';
import 'package:built_value/standard_json_plugin.dart';
import 'package:sitback/models/place.dart';
part 'serializers.g.dart';
#SerializersFor([
Place,
])
final Serializers serializers = _$serializers;
final Serializers standardSerializers =
(_$serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
Finally, here is the place where I want to consume a Firestore collection while using the Place "data class" defined via the built_value package:
class IndexPage extends StatelessWidget {
IndexPage({
Key? key,
}) : super(key: key);
final placesRef = FirebaseFirestore.instance
.collection('places')
.withConverter<Place>(
// TODO: ! at the end due to Place? vs Place in class definition
// would require a try-catch or something else?
fromFirestore: (snapshot, _) => Place.fromFirestore(snapshot.data()!)!,
toFirestore: (place, _) => place.toFirestore(),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: StreamBuilder<QuerySnapshot<Place>>(
stream: placesRef.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final itemData = snapshot.data!.docs[index];
return PlaceCard(itemData: itemData);
},
childCount: snapshot.data!.docs.length,
),
),
],
);
}
},
),
),
);
}
}
It is not working currently because of this line:
toFirestore: (place, _) => place.toFirestore(),. If I return {} from toFirestore(), then it compiles and works
BUT I suspect fromFirestore() is not even called: if I place assert(false); inside the function, nothing happen.. I'd except an exception.
You can't use a serialiser for Firestore. You need to manually create a map from your built value object to upload it. Once you want to fetch from Firebase you need to create a built object from a map. Haven't done it recently but it would be something like it:
static Place _fromFirestore(Map<dynamic, dynamic> placeMap) {
final List<dynamic> methods = placeMap['methods'] ?? [];
final place = Place((b) => b
..id = placeMap['id']
..name = placeMap['name']
..address = placeMap['address']
..methods.addAll(List<String>.from(methods.cast<String>()));
return place;
}
Related
So i have my dart call to my api get method. Btw the way am just learning flutter and dart and trying out basic crud operations I would use to be doing in .net and c#
import 'dart:convert';
import 'package:theapp/models/Players.dart';
import 'package:http/http.dart';
class ApiService {
final String apiUrl = "https://apiurlhidden.com/api";
final String getAllPlayersEndPoint = "/GetAllPlayers/";
Future<List<Player>> getAllPlayers() async {
final getallPlayersUrl = Uri.parse(apiUrl + getAllPlayersEndPoint);
Response res = await get(getallPlayersUrl);
if (res.statusCode == 200) {
List<dynamic> body = jsonDecode(res.body);
List<Player> players =
body.map((dynamic item) => Player.fromJson(item)).toList();
return players;
} else {
throw "Failed to load cases list";
}
}
}
And I have my listview here but it complaining saying key and players do not exist
import 'package:flutter/material.dart';
import 'package:theapp/models/Players.dart';
class PlayerList extends StatelessWidget {
List<Player> players = [];
PlayerList({Key key, this.players}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: players == null ? 0 : players.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () {},
child: ListTile(
leading: Icon(Icons.person),
title: Text(players[index].firstName),
subtitle: Text(players[index].surname.toString()),
),
));
});
}
}
My Model
class Player {
final int id;
final int type;
final String playerLevel;
final String firstName;
final String surname;
Player(this.id, this.type, this.playerLevel, this.firstName, this.surname);
factory Player.fromJson(Map<String, dynamic> json) {
return Player(
json['id'],
json['type'],
json['playerlevel'],
json['firstname'],
json['surname'],
);
}
#override
String toString() =>
'Players{id: $id, firstName: $firstName, lastName: $surname}';
}
Is there any reason why it should not recognize players and key in my list view page also how do I get the items to appear in the listview.
Picture only added to show the context in the items I mentioned above. Also coming from a .net background I would normally use an observable collection so it gets any changes in data in real-time am I using the correct approach for that.
Use required keyword to make parameters mandatory.
PlayerList({required Key key, required this.players}) : super(key: key);
Named parameters are optional unless they’re explicitly marked as required.
See Parameters for details.
I am building a chat app with the tutorial I downloaded from github, but since it is made by firestore, and people suggests to user firebase RTDB, so now Im transforming all the related code, one problem I met is followings:
This is my code:
static Stream<List<User>> getUsers() {
return usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final UserList = User.fromJson(data).toList();
return UserList;
});
}
I wan to use methode getUsers() for this following widget:
Widget build(BuildContext context) =>
Scaffold(
backgroundColor: Colors.blue,
body: SafeArea(
child: StreamBuilder<List<User>>(
stream: FirebaseApi.getUsers(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
default:
if (snapshot.hasError) {
print(snapshot.error);
return buildText('Something Went Wrong Try later');
} else {
final users = snapshot.data;
if (users.isEmpty) {
return buildText('No Users Found');
} else
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
}
}
},
),
),
);
This is original code made for firestore, which I want to use my code to replace:
static Stream<List<User>> getUsers() => FirebaseFirestore.instance
.collection('users')
.orderBy(UserField.lastMessageTime, descending: true)
.snapshots()
.transform(Utils.transformer(User.fromJson));
So here comes error which makes me crying:
A value of type 'StreamSubscription<DatabaseEvent>' can't be returned from the method 'getUsers' because it has a return type of 'Stream<List<User>>'.
Plz, plz help me if you have any clue how to use firebase rtdb, thanks a lot, and btw why there is so many firestore tutorial for chat app which will be more expensive instead of rtdb.
Thanks a lot in advance and keep safe!
Updated after several experiment, Im not sure if following is correct solution:
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
for user.fromJson is followings code:
static User fromJson(Map<String, dynamic> json) => User(
idUser: json['idUser'],
name: json['name'],
urlAvatar: json['urlAvatar'],
lastMessageTime: Utils.toDateTime(json['lastMessageTime']),
);
So it means I transfer the data from Json to List, do I understand it correctly? Thanks for explaining, it is very kind of this community, Im just a software beginner but older than 35:)
updated after despairing experiment since above return an error:
This function has a return type of 'Stream<List<User>>', but doesn't end with a return statement.
I tried another solution which use another widget:
Widget build(BuildContext context) {
return FirebaseAnimatedList(
query: _usersReference.child("timestamp"),
sort: (a, b) => (b.key.compareTo(a.key)),
defaultChild: new CircularProgressIndicator(),
itemBuilder: (context, snapshot, animation, index) {
final data = Map<String, dynamic>.from(snapshot.value);
final List<User> users = data.entries.map((e) => e.value).toList();
return Column(
children: [
ChatHeaderWidget(users: users),
ChatBodyWidget(users: users)
],
);
});
}
so from my poor understanding query: _usersReference.child("timestamp"),will give me a map and I just need to convert to a List to ChatHeaderWidget(users: users), is it correct?
Sorry for my long question and diary, I can not test it now, since there are too many error yet.
Stream<List<User>> getUsers() {
getUserStream = usersReference.onValue.listen((event){
final data = Map<String, dynamic>.from(event.snapshot.value);
final userList = User.fromJson(data);
return userList;
});
}
There is no return value in this method. usersReference.onValue is a stream, you have to return with that. And for example you can use Stream.map() method to convert stream events to user list you can use in the StreamBuilder.
So one possible solution is the following:
Stream<List<User>> getUsers() =>
FirebaseDatabase.instance.ref().onValue.map((event) =>
event.snapshot.children
.map((e) => User.fromJson(e.value as Map<String, dynamic>))
.toList());
I imagined your data structure is something like this:
"users": {
"userId1": { /* userData */ },
"userId2": { /* userData */ },
"userId3": { /* userData */ }
}
Now you receive realtime database changes in your StreamBuilder. You have a list of users so I think your next step in your learning path to show these users on the screen. If you want to test with Column, you have to generate all children of it. For example you can use the map method on the user list too.
Column(children: userList.map((user) => ListTile(title: Text(user.name))).toList())
or another solution
Column(children: [
for (var user in users)
ListTile(title: Text(user.name))
])
Here is the Code where I am facing the error am learning from a source and the coding version is 2018 so that's why am facing the error.
Basically its not an error its a confusion what to do with this. Am building a testing app for my personal use. Here is the error
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:myapp/fake_data.dart';
import 'package:myapp/food.dart';
import 'models/category.dart';
class FoodPage extends StatelessWidget
{
static const String routename='/FoodPage';
Category? category;
FoodPage({this.category});
#override
Widget build(BuildContext context) {
Map<String, Category>? arguments=ModalRoute.of(context)!.settings.arguments as
Map<String, Category>?;
category=arguments!['category'] ;
Iterable<Food> foods = FAKE_FOOD.where((food) =>
food.categoryId==this.category!.id).toList();
return Scaffold(
appBar: AppBar(
title: Text('Foods From ${category!.content}'),
),
body: Center(
child: Center(
child: ListView.builder(
itemCount: foods.length,
itemBuilder: (context,index){
Food food=foods[index];
return Text(food.urlImage);
}),
)
),
);
}
}
error is in "Food food=foods[index];"
Here is food Class where I derive the food class
import 'dart:math';
class Food {
int? id;
String name;
String urlImage;
Duration duration;
Complexity? complexity;
List<String>? ingredients=<String>[];
int? categoryId;
Food({this.id,
required this.name,
required this.urlImage,
required this.duration,
this.complexity,
this.categoryId,
this.ingredients});
{
this.id=Random().nextInt(100);
}
}
enum Complexity
{
Simple,Medium,Hard
}
I am pretty sure this line is what is causing the error:
Iterable<Food> foods = FAKE_FOOD.where((food) =>
food.categoryId==this.category!.id).toList();
The problem is that you cannot assign a List to a variable of the type Iterable (just like the error tells you). Since you are casting anyway I would recommend working with a List in general:
List<Food> foods = FAKE_FOOD.where((food) =>
food.categoryId==this.category!.id).toList();
Now we are assigning a List to a variable of the type List which fixes this error.
I've looked around in StackoverFlow and was not able to find myself a solution to this.
Scenario:
I have a Flutter SharedPreferences Provider with ChangeNotifier Class, that will get updated with the current Logged In User info.
Simplified content:
class SharedPreferences {
final String userId;
final String userName;
SharedPreferences({
#required this.userId,
#required this.userName,
});
}
class SharedPreferencesData with ChangeNotifier {
var _sharedPreferencesData = SharedPreferences(
userId: 'testUserId',
userName: 'testUserName',
);}
And a database.dart file with Class containing DataBaseServices to get FireStore Streams from Snapshots:
class DatabaseService {
final CollectionReference companiesProfilesCollection =
Firestore.instance.collection('companiesProfiles');
List<CompanyProfile> _companiesProfilesFromSnapshot(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return CompanyProfile(
docId: doc.documentID,
companyName: doc.data['companyName'] ?? '',
maxLocationsNumber: doc.data['maxLocationsNumber'] ?? 0,
maxUsersNumber: doc.data['maxUsersNumber'] ?? 0,
);
}).toList();
}
Stream<List<CompanyProfile>> get getCompaniesProfiles {
return companiesProfilesCollection
.where('userId', isEqualTo: _userIdFromProvider)
// My problem is above -----
.snapshots()
.map(_companiesProfilesFromSnapshot);
}
}
I Don't want to fetch the entire Stream data as it could be massive for other Streams, I just want to pass the userID under .where('userId', isEqualTo:_userIdFromProvider).
I couldn't access the context in this class to get the data from the Provider
Couldn't send the userId to getCompaniesProfiles getter, as getter don't take parameters
And if I convert this getter to a regular method, I wont be able to send the userID to it, as this has to run under void main() {runApp(MyApp());} / return MultiProvider(providers: [ and By then I cannot call fetch the sharedPreferences with a context that does not contain the provider info ...
Couldn't figure out how to receive the context as a constructor in this class, when I did, I got the following Only static members can accessed in initializers in class DatabaseService.
I'm still a beginner, so I would appreciate if you could share with me the best approach to handle this.
Thank you!
*********** Re-Edited by adding the below: **************
I'm trying to implement the same scenario, here is my code:
Main file:
return MultiProvider(
providers: [
ChangeNotifierProvider<SpData>(
create: (context) => SpData(),
),
ProxyProvider<SpData, DS>(
create: (context) => DS(),
update: (ctx, spData, previousDS) {
print('ChangeNotifierProxyProvider RAN');
previousDS.dbData = spData;
return previousDS;
},
),
],
SP File:
class SP {
final String companyId;
SP({
#required this.companyId,
});
}
class SpData with ChangeNotifier {
var _sPData = SP(
companyId: '',
);
void setCompanyId(String cpID) {
final newSharedPreferences = SP(
companyId: cpID,
);
_sPData = newSharedPreferences;
print('_spData companyId:${_sPData.companyId}');
notifyListeners();
}
String get getCompanyId {
return _sPData.companyId;
}
}
DS file:
class DS with ChangeNotifier {
SpData dbData;
void printCompanyId() {
var companyId = dbData.getCompanyId;
print('companyId from DataBase: $companyId');
}
}
The SpData dbData; inside Class DS does not update. I've added the prints to figure out what is running and what is not. When I run my code, the print function in main.dart file print('ChangeNotifierProxyProvider RAN'); does not run.
What am I missing? Why ChangeNotifierProxyProvider is not being triggered, to update dbData inside DS file? Thanks!
You can use ProxyProvider for this purpose.
ProxyProvider is a provider that builds a value based on other providers.
You said you have a MultiProvider, so I guess you have SharedPreferencesData provider in this MultiProvider and then DatabaseService provider. What you need to do is use ProxyProvider for DatabaseService instead of a regular provider and base it on the SharedPreferencesData provider.
Here is an example:
MultiProvider(
providers: [
ChangeNotifierProvider<SharedPreferencesData>(
create: (context) => SharedPreferencesData(),
),
ProxyProvider<SharedPreferencesData, DatabaseService>(
create: (context) => DatabaseService(),
update: (context, sharedPreferencesData, databaseService) {
databaseService.sharedPreferencesData = sharedPreferencesData;
return databaseService;
},
dispose: (context, databaseService) => databaseService.dispose(),
),
],
child: ...
Here is what happens in the code snippet above:
ProxyProvider calls update everytime SharedPreferencesData changes.
DatabaseService gets its sharedPreferencesData variable set inside update.
Now that you have access to sharedPreferencesData inside the DatabaseService instance, you can do what you want easily.
I have written a custom type "Tracks", and am reading the track from a JSON file and then converting into a List of <Tracks>.
Here is a portion of the code (which throws the error on the 4th line):
Future loadTrackList() async {
String content = await rootBundle.loadString('data/titles.json');
List<Tracks> collection = json.decode(content);
List<Tracks> _tracks = collection.map((json) => Tracks.fromJson(json)).toList();
setState(() {
tracks = _tracks;
}); }
Additionally, here is the tracks.dart file where I have serialized the JSON.
class Tracks{ final String title; final String subtitle;
Tracks(this.title, this.subtitle);
Tracks.fromJson(Map<String, dynamic> json) :
title = json['title'],
subtitle = json['subtitle']; }
And in my original usage scenario, I use this list of tracks in this manner:
body: ListView.builder(
itemCount: tracks.length,
itemBuilder: (BuildContext context, int index) {
var rnd = new Random();
var totalUsers = rnd.nextInt(600);
Tracks trackTitles = tracks[index];
return PrimaryMail(
iconData: OMIcons.supervisorAccount,
title: trackTitles.title,
count: "$totalUsers active",
colors: Colors.lightBlueAccent,
);
},
),
Now, firstly - in the first bunch of code inside the async loader, I am getting the error error: The argument type 'Tracks' can't be assigned to the parameter type 'Map<String, dynamic>'. for the line
List<Tracks> _tracks = collection.map((json) => Tracks.fromJson(json)).toList();
Additionally, when I use the track here:
title: trackTitles.title, // inside listView builder
I get the error (when running the app, not before): type '_InternalLinkedHashMap<String, dynamic is not a subtype of type 'tracks'
Requesting for any kind of help regarding how to get rid of this issue. You can find the important part of the whole code in this link for a good look into the implementation.
Try this
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:news_app/tracks.dart';
class TrackList extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TrackListState();
}
class _TrackListState extends State<TrackList> {
List<dynamic> tracks = List<dynamic>();
Future loadTrackList() async {
String content = await rootBundle.loadString('data/titles.json');
var collection = json.decode(content);
print(collection);
List<dynamic> _tracks =
collection.map((json) => Tracks.fromJson(json)).toList();
setState(() {
tracks = _tracks;
});
}
void initState() {
loadTrackList();
super.initState();
}
#override
Widget build(BuildContext context) => Scaffold(
backgroundColor: Colors.white,
body: Container(
child: ListView.builder(
itemCount: tracks.length,
itemBuilder: (BuildContext context, int index) {
Tracks item = tracks[index];
return Text(item.title);
},
),
),
);
}