Get data from firestore and convert to json and use that in factory constructor - flutter

I am using a firestore as database. If the data not present in database then it will do webscrape. In webscrape I managed to convert the data into json and used factory constructor. I want the same thing should happen while fetching data from firestore.
One More thing I have a specific collection and document id So I used .collection('medicine').doc('id').get().
Factory Constructor
class Tablet {
String name;
String introduction;
Tablet({
this.name,
this.introduction,
});
factory Tablet.fromJson(Map<String, dynamic> json) {
return Tablet(
name: json['name'],
introduction: json['introduction'],
);
}
}
Method which get Data from Database If Data Present
This is a Method which must returns Future<Tablet> (I stuck in if condition)
Future<Tablet> details;
Future<Tablet> getDetails(String medName) async {
await Firebase.initializeApp();
await FirebaseFirestore.instance
.collection('medicine')
.doc(medName.toLowerCase())
.get()
.then((DocumentSnapshot docSnapshot) {
if (docSnapshot.exists) {
var json = jsonDecode(docSnapshot.data().toString()); // I am getting stuck here
details = Tablet.fromJson(json) as Future<Tablet>;
} else {
print('Data not present in Database..');
details = webScrape(medName);
}
});
return details;
}
What I Tried in webscrape(medName) is
Tablet.fromJson(jsonDecode(response.body));
Here returning above line and assigning it to Future works but in the if condition, It is asking for type cast and the following error thrown
Error
FormatException: Unexpected character (at character 2)
{benefits: {values: [{header: In Heartburn, display_text: Heartburn and aci...
^
Update
As #Tarik Huber 2nd suggestion, Inspired and changed a code little bit as shown
factory Tablet.fromSnapshot(DocumentSnapshot docSnap){
return Tablet(
name: docSnap.get('name');
introduction: docSnap.get('introduction');
);
}
Now My Question is, the below code isn't working
details = Tablet.fromSnapshot(docSnapshot.data()) as Future<Tablet> // is not working
instead calling async function did as shown
details = arrangeData(docSnapshot.data());
Future<Tablet> arrangeData(DocumentSnapshot data) async{
return Tablet.fromSnapshot(data);
}
I know It's not a good approach and It is working but don't know How? Can anyone Explain..

Have you tried it with:
details = Tablet.fromJson(docSnapshot.data()) as Tablet;
the data is of type Map<String,dynamic>. That is the same type your convertion functions takes.
Othervise you could just add a
factory Tablet.fromSnapshot(DocumentSnapshot docSnapshot) {
return Tablet(
name: docSnapshot.get('name'),
introduction: docSnapshot.get('introduction'),
);
}

Related

Why changes made to this class field disappear after leaving async method?

So, I'm trying to do something that seems simple, but I can't get why it is not working.
I wrote this code:
class HomepageModel {
List<dynamic> data = [];
String _url = '[...]'; //My URL is working
Future _comm() async {
data = await httpFetch(_url); //This 'httpFetch' just does some json decoding
print(data); //Shows data normally
}
HomepageModel() {
_comm();
print(data); //Shows empty list
}
}
I want to store data retrieved from an API in the field data. I was able to get the data in function _comm, and when I print the field, it shows normally. But, outside this method, the content of the data field seems to have disappeared! Both on the print in the constructor, and in other files, all the prints show just an empty list.
What should I do to be able to properly store data from the API in a class field?
class HomepageModel {
List<dynamic> data = [];
String _url = '[...]'; //My URL is working
Future _comm() async {
data = await httpFetch(_url); //This 'httpFetch' just does some json decoding
print(data); //Shows data normally
}
HomepageModel() {
_comm().then((_){
print(data);
});
}
}

The method 'forEach' can't be unconditionally invoked because the receiver can be 'null'

What I am trying to do is to fetch the value of the key on my Firebase Database.
This is my code
import 'package:firebase_database/firebase_database.dart';
import 'package:comment_app/post.dart';
final dataReference = FirebaseDatabase.instance.ref().child('posts/');
DatabaseReference savePost(Post post) {
final id = dataReference.child('posts/').push();
id.set(post.toJson());
return id;
}
void updatePost(Post post, DatabaseReference id) {
id.update(post.toJson());
}
Future<List<Post>> getAllMessages() async {
DataSnapshot? dataSnapshot =
(await dataReference.child('posts/').once()) as DataSnapshot;
List<Post> posts = [];
if (dataSnapshot.value != null) {
dataSnapshot.value.forEach((key, value){ //Error highlights the forEach here.
Post post = createPost(value);
post.setId(dataReference.child('posts/' + key));
posts.add(post);
});
}
return posts;
}
Here is the code for post.dart
import 'package:comment_app/authentication/database.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
class Post {
String body;
String author;
Set userLikes = {};
late DatabaseReference _id;
Post(this.body, this.author);
void likePost(User users) {
if (userLikes.contains(users.uid)) {
userLikes.remove(users.uid);
} else {
userLikes.add(users.uid);
}
this.update();
}
void update() {
updatePost(this, _id);
}
void setId(DatabaseReference id) {
_id = id;
}
Map<String, dynamic> toJson() {
return {
'author': author,
'userLikes': userLikes.toList(),
'body': body,
};
}
}
Post createPost(record) {
Map<String, dynamic> attributes = {'author': '', 'userLikes': [], 'body': ''};
record.forEach((key, value) => {attributes[key] = value});
Post post = Post(attributes['body'], attributes['author']);
post.userLikes = Set.from(attributes['userLikes']);
return post;
}
I'm just starting to learn flutter with firebase and I am getting this error:
This is the error
The method 'forEach' can't be unconditionally invoked because the
receiver can be 'null'. Try making the call conditional (using '?.')
or adding a null check to the target ('!').
I'm trying to get the author and body of each key in my firebase database
So the error is telling you that you want to run something.forEach. but flutter doesn't know if something is null, if it is null, it can't run foreach, so it is asking you to tell it what it should do if it is null. We first need to figure out what something is. You call .forEach twice in your code:
dataSnapshot.value.forEach
and
record.forEach
I don't know which might be causing the error, but if you look at the error message, it should tell you the exact line and file it is happening, so do try to figure it out by yourself.
On the first call, you already made sure dataSnapshot.value is not null by adding this line:
if (dataSnapshot.value != null) {
Normally, dart is smart enough to figure out dataSnapshot.value is not null, but I guess there is a chance it didn't, in such cases, where you know a value that could be null, isn't, you can use the ! expression after a null value.
dataSnapshot.value!.forEach
Bare in mind that if dataSnapshot.value was null, your app would crash, so only use ! when you know a value is not null. Also, as I said, dart is probably smart enough to know dataSnapshot.value is not null, so you probably don't need to do this, and the real culprit is probably record.
The second time you use it is in record.forEach, so let's look at the type of record to figure out weather it can be null or not:
Post createPost(record) {
...
}
What's this? You did not give record any type! No wonder dart doesn't trust that it won't be null. Whatever record is supposed to be, make sure to add a type, if it can be null, then you should add the a null check before running like you did on dataSnapshot
1. Dart doesn't recognize that you already checked if
dataSnapshot.value is null:
if (dataSnapshot.value != null) {
dataSnapshot.value.forEach((key, value){
To fix this issue you can simply add '!' in front of value:
dataSnapshot.value!.forEach((key, value){
2. The forEach method comes up as undefined:
The method 'forEach' isn't defined for the type 'Object'.
Try correcting the name to the name of an existing method, or defining a method named 'forEach'.dartundefined_method
This happens because forEach doesn't normally work inside a Future<T> function.
To get forEach to work as intended, you'd need to invoke it from Future first, which in this case won't work because you must pass dataSnapshot.value to the forEach() method and doing so breaks the code.
Future.forEach(elements, (element)); // This is an example of what it would look like
A typical way to deal with this issue is to restructure the code to fit a for loop or try something different.
I found that separating the forEach() method from a Future<T> function to a Future function to get it to run normally works just as well.
I had the same problem recently when trying a newer version of firebase, this was how I fixed it:
// New function using the regular Future, forEach() works normally
Future snapshotValue(dataSnapshot, posts) async {
dataSnapshot.value!.forEach((key, value) {
Post post = createPost(value);
post.setId(databaseReference.child("posts/" + key));
posts.add(post);
});
}
// Future<t> function, only supports Future.forEach()
Future<List<Post>> getAllMessages() async {
DataSnapshot dataSnapshot = (await databaseReference.child("posts/").once()).snapshot;
List<Post> posts = [];
if (dataSnapshot.value != null) {
snapshotValue(dataSnapshot, posts); // Insert the new function back in
}
return posts;
}
The code above was tested on firebase_database: ^9.1.2 and firebase_auth: ^3.6.4.

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.

Cannot access decoded json body flutter

I'm fetching data in flutter and created a helper class as seen below
import 'dart:convert';
import 'package:http/http.dart' as http;
class HttpClient {
final Map<String, String> _headers = {};
void setHeader(String key, String value) {
if (!_headers.containsKey(key)) {
_headers[key] = value;
}
}
Future<http.Response> get(String url) async {
try {
var uri = Uri.parse(url);
var response = await http.get(uri, headers: _headers);
if (response.statusCode == 200) {
dynamic result = jsonDecode(response.body);
return result;
} else {
throw Error();
}
} on Exception catch (_) {
rethrow;
}
}
}
HttpClient appClient = HttpClient();
I call the method from a class as shown below
abstract class AbstractTodoService {
dynamic getTodos() {}
}
class HttpTodoService implements AbstractTodoService {
#override
Future<Response> getTodos() async {
try {
var todos =
await appClient.get('https://jsonplaceholder.typicode.com/todos');
print(todos);
return todos;
} on Exception catch (error) {
print(error);
rethrow;
}
}
}
However, when I print todos or any string in the HttpTodoService after the await call to the HttpClient I do not see anything. However, when I print the result inside the HttpClient I see the response but does not return. When I return a normal string or map everything works normally but when I attempt to use the jsonDecoded response nothing returns.
Your jsonDecode(response.body); returns a List<dynamic> type, but your function return type is Future<http.Response>. This is why you are not getting data.
You can check runtime datatype of a variable by
print(result.runtimeType); // variable_name.runtimeType
Change function return types to Future<List<dynamic>> of get(String url) and getTodos() functions.
It might be because your function returns a future of http.Response which is actually the type of the response after you use http.get. After you use jsonDecode you should get a Map<String, dynamic> which represent the json.
From the documentation:
By looking at the dart:convert documentation, you’ll see that you can decode the JSON by calling the jsonDecode() function, with the JSON string as the method argument.
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
Unfortunately, jsonDecode() returns a Map<String, dynamic>, meaning that you do not know the types of the values until runtime. With this approach, you lose most of the statically typed language features: type safety, autocompletion and most importantly, compile-time exceptions. Your code will become instantly more error-prone.
For example, whenever you access the name or email fields, you could quickly introduce a typo. A typo that the compiler doesn’t know about since the JSON lives in a map structure.
After you use jsonDecode you should turn the map into the object you want to work with using factory fromJson method.
You can read more about is in the documentation https://flutter.dev/docs/development/data-and-backend/json

How to use a String returned by a Future<String> method in flutter?

I'm trying to read a json file's components using the following method:
import 'dart:io';
class CharacterDataReader {
Future<String> read() async {
final file = File('assets/data/character_data.json');
String data = await file.readAsString();
return data;
}
}
Now, I'm trying to assign the read values to a String named data and json.decode() it in another class using the following:
Future<String> data = CharacterDataReader().read();
Map<String, dynamic> characterData = json.decode(data);
However, this doesn't work since json.decode() only accepts Strings as a parameter. Therefore, can someone please tell me how do I convert this Future into an actual string?
since its a future you have to add await keyword
String data= await CharacterDataReader().read();
check out dart official doc on asynchronous programming