How do I load a single entry in Sembast based on it's ID? - flutter

I look through a bunch of tutorials for Sembast. The tutorials give examples about how to get all the items of a certain class like Cake but not how to load a specific one based on it's ID:
#override
Future<List<Cake>> getAllCakes() async {
final snapshots = await _store.find(_database);
return snapshots
.map((snapshot) => Cake.fromMap(snapshot.key, snapshot.value))
.toList(growable: false);
}
How do I lot a single Cake for a given ID?

You can use store.record(key).get(db)
Assuming your key is an int, this could look like this:
Future<Cake> getCake(int key) async {
final snapshot = await _store.record(key).getSnapshot(_database);
return snapshot != null ? Cake.fromMap(key, snapshot.value) : null;
}
More information (yes documentation is not that great) here

Related

What is the best way to work with files in Flutter?

I'm a junior working with flutter and hit a problem.
I need to open a file, read and compare some data everytime the app opens and then change some of that data as the app progress. We tried using .txt files to read and write some text, but when we had to look for something in the file was too complicated to change it and the file is not accessibe only on the device running the app. We also thought of using xml files but I don't know if is a good idea.
What would be a pratical solution for this situation, as the file needs to be opened all the time. Thanks.
Let's say our JSON looks like this:
{
title: 'some text',
values: [1,5,2,4,1,3],
}
And we want to make a UI that allows us to add values and to edit the title. First let's write a method to write and read from the JSON file:
Future<void> _write(Map<String, dynamic> value) async {
File f = File(_fileLocation);
String jsonStr = jsonEncode(value);
await f.writeAsString(jsonStr);
}
Future<Map<String, dynamic>> _read() async {
File f = File(_fileLocation); // maybe move this into a getter
final jsonStr = await f.readAsString();
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
This way, the rest of the app should be trivial, but let's add a method to update the title and a method to add a new number:
Future<void> _updateTitle(String title) async {
var values = await _read();
values['title'] = title;
await _write(values);
}
Future<void> _addNumber(int number) async {
var values = await _read();
values['values'].push(number);
await _write(values);
}
Types with JSON and Dart can be a bit weird, so it is possible you need to use the as keyword when reading from the list:
Future<void> _addNumber(int number) async {
var values = await _read();
var valueList = (values['values'] as List<int>);
valueList.push(number);
values['values'] = valueList;
await _write(values);
}
Hopefully, this example helps

transform from Firestore API to firebase rtdb api

recently im learning to create a event from a tutorial, but the original one is made in firestore, and Im trying to use firebase rtdb,
here is original code:
class FirebaseApi {
static Future<String> createTodo(Todo todo) async {
final docTodo = FirebaseFirestore.instance.collection('todo').doc();
todo.id = docTodo.id;
await docTodo.set(todo.toJson());
return docTodo.id;
}
}
here is code I created, sorry my basic knowledge is now good, Idk what should i return
class FirebaseApi {
static Future<String> createTodo(Todo todo) async{
final todoRefMessages = FirebaseDatabase.instance.ref().child('todo');
final newTodo = FirebaseDatabase.instance.ref().child('todo').get().then((snapshot) async{
final json = Map<dynamic, dynamic>.from(snapshot.value);
final newTodo = Todo(
createdTime: Utils.toDateTime(json['createdTime']),
title: json['title'],
description: json['description'],
id: json['id'],
isDone: json['isDone'],
);
await todoRefMessages.set(newTodo.toJson());
return newTodo;
});
todo.id= newTodo.id;//here got error, The getter 'id' isn't defined for the type 'Future<Todo>
return newTodo.id;
}
}
could you please let me know how to create same method but for firebase rtdb, thanks in advance!
This call to Firestore give you a reference to a new non-existing document in the todo collection:
final docTodo = FirebaseFirestore.instance.collection('todo').doc();
Is equivalent to this in the Realtime Database is:
final todoRef = FirebaseDatabase.instance.ref("todo").push();
Then to store the JSON to that document in Firestore you do:
await docTodo.set(todo.toJson());
The equivalent in the Realtime Database is:
await todoRef.set(todo.toJson());
If you have errors in other parts of your code, I recommend keeping the functionality of your createTodo method exactly the same between the Firestore and Realtime Database implementations.
For example, I assume that the Todo todo object is exactly the same between the implementations. If it isn't, the problem is less likely to be in the database API calls, but probably in the implementation of Todo.

Flutter pagination with firestore stream

How to properly implement pagination with firestore stream on flutter (in this case flutter web) ?
my current approach with bloc which is most likely wrong is like this
function called on bloc when load next page, notice that i increased the lastPage variable of the state by 1 each time the function is called:
Stream<JobPostingState> _loadNextPage() async* {
yield state.copyWith(isLoading: true);
try {
service
.getAllDataByClassPage(state.lastPage+1)
.listen((List<Future<DataJob>> listDataJob) async {
List<DataJob?> listData = [];
await Future.forEach(listDataJob, (dynamic element) async {
DataJob data= await element;
listData.add(data);
});
bool isHasMoreData = state.listJobPostBlock.length!=listData.length;
//Update data on state here
});
} on Exception catch (e, s) {
yield StateFailure(error: e.toString());
}}
function called to get the stream data
Stream<List<Future<DataJob>>> getAllDataByClassPage(
String className, int page) {
Stream<QuerySnapshot> stream;
if (className.isNotEmpty)
stream = collection
.orderBy('timestamp', "desc")
.where('class', "==", className).limit(page*20)
.onSnapshot;
else
stream = collection.onSnapshot;
return stream.map((QuerySnapshot query) {
return query.docs.map((e) async {
return DataJob.fromMap(e.data());
}).toList();
});
}
With this approach it works as intended where the data loaded increased when i load next page and still listening to the stream, but i dont know if this is proper approach since it replace the stream could it possibly read the data twice and end up making my read count on firestore much more than without using pagination. Any advice is really appreciated, thanks.
Your approach is not very the best possible indeed, and as you scale you going to be more costly. What I would do in your shoes would be to create a global variable that represents your stream so you can manipulate it. I can't see all of your code so I am going to be as generic as possible so you can apply this to your code.
First let's declare the stream controller as a global variable that can hold the value of your stream:
StreamController<List<DocumentSnapshot>> streamController =
StreamController<List<DocumentSnapshot>>();
After that we need to change your getAllDataByClassPage function to the following:
async getAllDataByClassPage(String className) {
Stream stream = streamController.stream;
//taking out of the code your className logic
...
if(stream.isEmpty){
QuerySnapshot snap = await collection.orderBy('timestamp', "desc")
.where('class', "==", className)
.limit(20)
.onSnapshot
streamController.add(snap.docs);
}else{
DocumentSnapshot lastDoc = stream.last;
QuerySnapshot snap = await collection.orderBy('timestamp', "desc")
.where('class', "==", className)
.startAfterDocument(lastDoc)
.limit(20)
.onSnapshot;
streamController.add(snap.docs);
}
}
After that all you need to do in order to get the stream is invoke streamController.stream;
NOTE: I did not test this code but this is the general ideal of what you should try to do.
You can keep track of last document and if has more data on the list using startAfterDocument method. something like this
final data = await db
.collection(collection)
.where(field, arrayContains: value)
.limit(limit)
.startAfterDocument(lastDoc)
.get()
.then((snapshots) => {
'lastDoc': snapshots.docs[snapshots.size - 1],
'docs': snapshots.docs.map((e) => e.data()).toList(),
'hasMore': snapshots.docs.length == limit,
});

streaming and transforming a Firestore document snapshots() stream

I am trying to get a document stream from Firestore, to extract one of its fields in my repository and to stream the field to my bloc. I use return orderBriefDoc.snapshots().map((snapshot) { for this.
However, upon first call, no internal map instruction becomes executed and instead I receive a type mismatch type '_MapStream<DocumentSnapshot, dynamic>' is not a subtype of type 'Stream<List<OrderBrief>>'. I do not understand why the return type of the .map() method does not depend on what I return within its return statement and why this internal code is not executed.
First of all, I used the repository function of Felix Angelov's Firebase authentication ant of the todo list as a blueprint:
Stream<User> get user {
return _firebaseAuth.authStateChanges().map((firebaseUser) {
return firebaseUser == null ? User.empty : firebaseUser.toUser;
});
}
Stream<List<Todo>> todos() {
return todoCollection.snapshots().map((snapshot) {
return snapshot.documents
.map((doc) => Todo.fromEntity(TodoEntity.fromSnapshot(doc)))
.toList();
});
}
My adaption looks like this
#override
Stream<List<OrderBrief>> orderBriefs() {
if (orderBriefDoc == null)
getOrderCollection();
return orderBriefDoc.snapshots().map((snapshot) {
final tmp = snapshot;
print ("2");
final tmp2 = snapshot.data()['orderBriefs'];
print("3");
return snapshot.data()['orderBriefs'].map((orderBrief) {
final tmp=orderBrief;
final tmp2 = OrderBriefEntity.fromMap(orderBrief);
final tmp3 = OrderBrief.fromEntity(tmp2);
return tmp3;
}).toList();
});
}
For some reason "2" and "3" are not printed upon first call, and due to the type mismatch the app execution fails. So in my function orderBriefs() I return a .map() of a snapshots() stream. The mapped value, so the single document snapshot is mapped again to extract the orderBriefs field. This field is transformed from an storage entity class OrderBriefEntity to my business logic class OrderBrief. The transformation result is the final return value. Hence I would expect the function orderBriefs() to return a list stream of this transformation result. However, a _MapStream<DocumentSnapshot, dynamic> is returned. Why?
PS: This refers to my question, but with a slightly different angle
So, finally I found a method to stream a single document of Firestore. I finally had the idea to look up the documentation of Stream and found a working example there. Why it does not work with only the map method like Felix did it, no idea. That being said, I still follow his pattern to transform the snapshot to an "entity" and then further to the data structure used by the bloc and the ui.
I finally needed to flavors, stream a single field (nested array) of a document and stream a whole document
(1) Streaming a field of a document.
#override
Stream<List<OrderBrief>> orderBriefs() async* {
if (orderBriefDoc == null)
getOrderCollection();
Stream<DocumentSnapshot> source = orderBriefDoc.snapshots();
await for (var snapshot in source) {
final List<OrderBrief> returnVal = snapshot.data()['orderBriefs'].map<OrderBrief>((orderBrief) {
return OrderBrief.fromEntity(OrderBriefEntity.fromMap(orderBrief));
}).toList();
yield returnVal;
}
}
(2) Streaming a document with all of its fields
#override
Stream<Order> orderStream(String orderId) async* {
Stream<DocumentSnapshot> source = orderBriefDoc.collection('orders').doc(orderId).snapshots();
await for (var snapshot in source) {
final Order returnVal = Order.fromEntity(OrderEntity.fromSnapshot(snapshot));
yield returnVal;
}
}
How to neatly integrate this with a listener in the bloc, you will find in the Firestore ToDos example at bloclibrary

What is the best way to make a network call?

I am fairly new to Flutter. I would like to know what is the best way in terms of coding best practices to do a network call.
I searched on internet (including Stackoverflow) on how to make the REST call (GET, POST) and found some code samples. For example, one of them is given below.
new RaisedButton(
onPressed: () async {
Post newPost = new Post(
userId: "123", id: 0, title: titleControler.text, body: bodyControler.text);
Post p = await createPost(CREATE_POST_URL,
body: newPost.toMap());
print(p.title);
},
)
Now, I don't think it's a good idea to club everything in onPressed(). I am especially interested to know how to fit a network call before a page load (or update after the data is fetched). I know it's done by setState(). But would like to know how the different pieces are put together to write a best code.
Any help will be highly appreciated.
The best way is to keep every piece short and specific to one task only. This is not specific to the Flutter (and applies to any coding in general). One type of doing it is as mentioned below :
Let's say you want to fetch a list of employees from the API.
Define Employee class, specify mandatory/non-mandatory attributes etc. And also write .fromJson() to instantiate.
Start with didChangeDependencies in your Stateful widget like below.
#override
void didChangeDependencies() {
super.didChangeDependencies();
_requestEmployeeListFromAPI();
}
The _requestDataFromAPI() function will just be a pointer for the network call as follows :
Future<void> _requestEmployeeListFromAPI() async {
try {
_myList = await network.getEmployeeList();
} catch (exception) {
print(exception);
// ...
} else {
// show exception
}
} finally {
if (mounted) {
setState(() => _isLoadingCompleted = true);
}
}
}
Handle your UI based on the value of _isLoadingCompleted
Now, your network file may consists all the network calls to be done in the project. Example below :
Future<List<Employee>> getEmployeeList() async {
try {
http.Response response = await http.post(
"$getEmployeeListUrl",
);
Map<String, dynamic> decodedBody = json.decode(response.body);
print(decodedBody);
List<Employee> employeeList = (decodedBody["EmployeeList"] as List)
.map((element) => Employee.fromJson(element))
.toList();
return employeeList;
} catch (e) {
print(e);
}
}
This way, every piece is written at a different position and is very easy to enhance / debug / find bug, if any.