Store Firebase Data as JSON locally - firebase

I want to use JSON as a local database for my firebase data ,I don't want offline data store.I am using path_provider to store my data on the phone
So I tried storing data with
file.writeAsStringSync(json.encode(snapshot.toString()))
And it worked the stored data file looked like a JSON file
"{-key:{name:name,age:age},-key:{name:name,age:age}}"
The " " where missing and I can't decode it.
So what can be done here ?
(Edit:Does Firestore provide anything that can help me with this ?)

You can use the shared_preferences package to store JSON,
or if you want to write to a custom file use the path_provider package to get paths to directories where you app has permissions to write to.
You might also need to use the async versions of the dart:io API to access files in Flutter (not sure about that tough)
"{-key:{name:name,age:age},-key:{name:name,age:age}}" is not valid JSON
Either use
import 'dart:convert';
...
var myJson = {'-key':{'name:name','age:age'},'-key':{'name:name','age:age'}}";
var myJsonString = jsonEncode(myJson);
await file.writeAsString(myJsonString);
or
var myJsonString = '{"-key":{"name:name","age:age"},"-key":{"name:name","age:age"}}';
await file.writeAsString(myJsonString);

Create a JSON File
Future<String> get _localPath async {
final directory = await getExternalStorageDirectory();
return directory.path;
}
Future<File> get _dbFile async {
final path = await _localPath;
return new File("$path/demo.json");
}
Write data into a file
Future<File> write(DataSnapshot snapshot,String chatId) async {
final path = await _dbFile;
final String key = snapshot.key;
final String name = snapshot.value['senderUid'];
final int age= snapshot.value['receivedUid'];
String content = '{"$key":{"name":"$name","age":"$age"}}';
return file.writeAsStringSync(content);
}
Read Data
Future<Null> read() async {
try {
final file = await _dbFile;
String content = file.readAsStringSync();
Map<String, dynamic> chatMap=json.decode(content);
chatMap.keys.forEach((E){debugPrint(E);});
} catch (e) {
debugPrint('Error : '+e.toString());
}
}

Related

fetching user data from firebase and storing it in static variables

i am new to flutter and firebase development, so i really don't know how much will it cost me to keep fetching user data from firebase in every screen that i need them in, so i decided to fetch them once and store them in class MyUser static variables as follows:
in MyApp class:
bool isAuthenticated = false;
Future checkAuthenticity() async {
AuthService.getCurrentUser().then((user) async {
if (user != null) {
String myUid = await AuthService.getCurrentUID();
await MyUserController().getCurrentUserFromFirebase(myUid);
if (mounted)
setState(() {
isAuthenticated = true;
});
} else {
if (mounted)
setState(() {
isAuthenticated = false;
});
}
});
}
#override
Widget build(BuildContext context) {
home: isAuthenticated ? Home(passedSelectedIndex: 0) : Register(),
}
from the above code, this line await MyUserController().getCurrentUserFromFirebase(myUid); is as follows:
getCurrentUserFromFirebase(String uid) async {
await FirestoreService().getCurrentUserData(uid);
}
from the above code, this line await FirestoreService().getCurrentUserData(uid); is as follows:
Future getCurrentUserData(String uid) async {
try {
var userData = await FirebaseFirestore.instance.collection('users').doc(uid).get();
MyUser.fromData(userData.data());
} catch (e) {
if (e is PlatformException) {
return e.message;
}
return e.toString();
}
}
from the above code, this line MyUser.fromData(userData.data()); is a constructor in
MyUser class as follows:
class MyUser {
static String uid;
static String name;
static String username;
static String email;
static String userAvatarUrl;
static String location;
static String phoneNumber;
MyUser.fromData(Map<String, dynamic> data) {
uid = data['id'];
name = data['name'];
username = data['username'];
email = data['email'];
userAvatarUrl = data['userAvatarUrl'];
location = data['location'];
phoneNumber = data['phoneNumber'];
}
}
and to make use of all of the following, in each page that i need to load the current user data in, i use for example:
var userId = MyUser.uid
or to show the current user name i use Text('${MyUser.name}');
when i close the app completely and relaunch it again, it should check for authenticity, and complete executing the rest of the code in main() function.
so my questions are:
1) does this have any performance issues when we release the app?
2) does this will really will prevent unnecessary reads that i can consume in every page i need the data in ?
3) is there any better approach to prevent unnecessary reads from firebase, for example to save the current user data as strings and a profile image locally?
pardon me for prolonging the question, but i wanted to share the code itself.
any help would be much appreciated.
As a short answer,
You can make a class of SharedPreferences to store data as strings in key: value manner.
So anywhere you want you can get an instance of that class and reach it from anywhere in the app.
If you also declare some functions which will decode string to json you will get a ready user class instance in return of your function which will make it easier.
So when you want to save user info to Local Storage(SharedPreferences) you may use a function which will encode your User object to string and save it to SharedPreferences as below..
user.dart' as theUser; for conflict issues
class SharedPrefs {
static SharedPreferences _sharedPrefs;
init() async {
if (_sharedPrefs == null) {
_sharedPrefs = await SharedPreferences.getInstance();
}
}
dynamic get user=> _sharedPrefs.getString('user')!=null?theUser.User.fromString(_sharedPrefs.getString('user')):null;
set user(theUser.User user)=> _sharedPrefs.setString('user', jsonEncode(user));
String get accessToken=> _sharedPrefs.getString('access_token');
set accessToken(String accessToken)=> _sharedPrefs.setString('access_token', accessToken);
void removeString(String entry){
_sharedPrefs.remove(entry);
}
}
final sharedPrefs = SharedPrefs();
And in the app anywhere you can use it directly by typing sharedPrefs.user

How do I properly cast a response from a Firebase Function call in my Flutter app

I'm teaching myself Flutter by building a simple meal planner app. Part of what I want to do is to use a Firebase function for making calls to the API. I'm using the Spoonacular API, and I do not want to store the API key on the app itself, hence the Firebase backend.
I've set up a file called cloud_functions.dart, which I plan on using to make calls to my Firebase functions. The call to get the recipes is as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult<SearchRecipesComplexResponseBody> results = await callable({'number': 20, 'offset': 0});
print('Results: ');
print(results);
print('Results data:');
print(results.data);
return results.data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
HttpsCallable getCallable(String callableName) {
FirebaseFunctions functions = FirebaseFunctions.instance;
if (kDebugMode) {
print('Running in debug mode');
functions.useFunctionsEmulator(origin: 'http://localhost:5001');
}
return functions.httpsCallable(callableName);
}
The code for SearchRecipesComplexResponseBody is as follows:
import 'package:meal_planner/models/recipe.dart';
import 'package:json_annotation/json_annotation.dart';
part 'search_recipes_complex_response_body.g.dart';
#JsonSerializable()
class SearchRecipesComplexResponseBody {
final int offset;
final int number;
final List<Recipe> results;
final int totalResults;
SearchRecipesComplexResponseBody({this.offset, this.number, this.results, this.totalResults});
factory SearchRecipesComplexResponseBody.fromJson(Map<String, dynamic> json) {
return _$SearchRecipesComplexResponseBodyFromJson(json);
}
}
The code for Recipe is as follows:
#JsonSerializable()
class Recipe {
final int id;
#JsonKey(includeIfNull: false)
final int calories;
#JsonKey(includeIfNull: false)
final String carbs;
#JsonKey(includeIfNull: false)
final String fat;
final String image;
final String imageType;
#JsonKey(includeIfNull: false)
final String protein;
final String title;
Recipe({#required this.id, this.calories, this.carbs, this.fat, this.image, this.imageType, this.protein, #required this.title});
factory Recipe.fromJson(Map<String, dynamic> json) {
return _$RecipeFromJson(json);
}
}
While I do get the data back that I'm expecting, there's something going on with the casting that I get this error when running the code:
type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'
When I went to debug the code, breaking on the print(results) line in the cloud_functions.dart file, I saw that the data does seem to match the format that I'm expecting
I've attempted to use the json_serializable utility to generate the JSON serialization code, but that didn't work either. I've tried removing the extraneous fields in the Recipe class to no avail.
I think the issue is something to do with the fact that I've got a property on the SearchRecipesComplexResponseBody that's a list of Recipes, but I can't seem to figure out what I'm doing wrong here. For all I know, I could be barking up the wrong tree. Does anyone have any ideas?
RESOURCES CHECKED:
Flutterfire Cloud Functions documentation - https://firebase.flutter.dev/docs/functions/usage/
Search on HttpCallableResult - https://duckduckgo.com/?q=flutterfire+httpcallableresult&ia=web
https://medium.com/codespace69/flutter-working-with-data-json-json-and-serialization-f90165b659d0
I figured it out
I updated the getRecipes function in cloud_functions.dart to be as follows:
Future<SearchRecipesComplexResponseBody> getRecipes() async {
HttpsCallable callable = getCallable('searchRecipes');
try {
final HttpsCallableResult results = await callable({'number': 20, 'offset': 0});
var convertedResult = Map<String, dynamic>.from(results.data);
SearchRecipesComplexResponseBody data = SearchRecipesComplexResponseBody.fromJson(convertedResult);
return data;
} catch (e) {
print('Error: ');
print(e);
return null;
}
}
I saw that I already had a fromJson function defined on my SearchRecipesComplexResponseBody class, but I hadn't been taking advantage of it. I needed to convert the response I got back from Firebase from an _InternalLinkedHashMap<dynamic, dynamic> to the Map<String, dynamic> type that fromJson uses.
I also needed to add anyMap: true inside my JsonSerializer attributes to get the nested list of Recipes in order for its fromJson. I'm not sure why that worked. Anyone have any thoughts?
You may use this to convert from _InternalLinkedHashMap to Map<String, dynamic>. This will get you overcome the error type '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>':
HttpsCallable callable = functions.httpsCallable('your-function');
final results = await callable();
final data = Map<String, dynamic>.from(results.data);

Flutter: How to move a firebase node - API REST vs SDK

I have a node in Firebase that has multiple childs and childs of childs. I want to move all from one place to another, then I made a function with Firebase REST API, but I would like to optimize it and migrate it to SDK.
The function I'm using with API REST is:
Future<bool> moverLoteActual() async {
//TO READ FROM SOURCE PATH
final urlLoteActual = '$_PATH_ORIGEN.../loteActual.json?auth=${_prefs.token}';
final resp = await http.get(urlLoteActual);
//TODO: Probably I neew an error management here
//TO COPY IN DESTINATION
final urlLotesCerrados = '$PATH_DESTINO.../lotesCerrados.json?auth=${_prefs.token}';
final resp2 = await http.post(urlLotesCerrados, body: resp.body);
final decodedData2 = json.decode(resp2.body);
print(decodedData2);
//TO DELETE THE SOURCE NODE
final resp3 = await http.delete(urlLoteActual);
print(json.decode(resp3.body));
return true;
}
I tried with SDK but I get a lot of parsing errors and finally I think a sequential approach as I implemented with API REST is not the best way.
How can I get this change of location using SDK?
I found a solution.
First, you need to understand that snapshot.value is a Map<dynamic, dynamic>, then you need to assign the answer (resp) to this type of map.
Later, you need to copy the node in a new location, then you need to convert the previous Map to a new one of other type (Map<String, dynamic> to write)
The last step (delete the original node) is easy... the standard function.
Future<bool> moverLoteActual() async {
//TO READ FROM SOURCE PATH
Map<dynamic, dynamic> _map = new Map<dynamic, dynamic>();
Query resp = db.child('$_PATH_ORIGEN.../loteActual');
final snapshot = await resp.once();
_map = snapshot.value;
//TO COPY IN DESTINATION
db.child('$PATH_DESTINO.../lotesCerrados')
.push().update(Map<String, dynamic>.from(_map));
//TO DELETE THE SOURCE NODE
db.child('$_PATH_ORIGEN.../loteActual').remove();
return true;
}
PD. db is the Firebase Database Reference

How to write a value of Encrypted Datatype in a File in Flutter

I just used the following code to generate an encrypted value of a base64 string:
final encrypted = encrypter.encrypt(base64String, iv: iv);
I have used this package to use the above code: https://pub.dev/packages/encrypt
Now I want to save this data in firebase cloud storage, so I need to put this data in a file, but I have no idea how to write a value of Encrypted datatype in a file.
Please help me with this.
Thanks
You can write the string to a file as :
Future<File> writeData(string data) async {
final file = await _localFile;
// Write the file.
return file.writeAsString('$data');
}
where the _localFile is derived as :
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/filename.txt');
}
More details are available here.
Putting a local file to the Firebase Cloud Storage is a straight-forward job. Something like following should do :
Future<void> _uploadFile(File file, String filename) async {
StorageReference storageReference = FirebaseStorage.instance.ref().child("files/$filename");
final StorageUploadTask uploadTask = storageReference.putFile(file);
final StorageTaskSnapshot downloadUrl = (await uploadTask.onComplete);
final String url = (await downloadUrl.ref.getDownloadURL());
print("URL is $url");
}

Flutter moor insert hangs on isolate

When I create a database I want to initialize it with a ton of data.
I have the following initialization service.
// This needs to be a top-level method because it's run on a background isolate
DatabaseConnection _backgroundConnection() {
// construct the database. You can also wrap the VmDatabase in a "LazyDatabase" if you need to run
// work before the database opens.
final database = VmDatabase.memory();
return DatabaseConnection.fromExecutor(database);
}
Future<void> _initDatabase(Map<String, dynamic> args) async {
var moorIsolate = await MoorIsolate.spawn(_backgroundConnection);
var connection = await moorIsolate.connect();
var db = BillingDatabase.connect(connection);
_initBillingSpecialties(db, args["specialties"]);
}
Future<void> _initBillingSpecialties(BillingDatabase db, String specialtiesJson) async {
var json = jsonDecode(specialtiesJson);
var jsonSpecialties = json["specialties"] as List<dynamic>;
var specialities = jsonSpecialties.map((s) =>
DbSpecialtiesCompanion(name: Value(s["specialty_name"]),
mohNumber: Value(s["moh_specialty"]))).toList();
return db.specialtyDao.saveAllSpecialties(specialities);
}
#injectable
class InitDbService {
Future<void> initDatabase() async {
WidgetsFlutterBinding.ensureInitialized();
var specialties = await rootBundle.loadString("lib/assets/billing_specialties.json");
compute(_initDatabase, {"specialties": specialties});
//initDbSync(specialties);
}
Future<void> initDbSync(String specialtiesJson) async {
var json = jsonDecode(specialtiesJson);
var jsonSpecialties = json["specialties"] as List<dynamic>;
var specialities = jsonSpecialties.map((s) =>
DbSpecialtiesCompanion(name: Value(s["specialty_name"]),
mohNumber: Value(s["moh_specialty"]))).toList();
var dao = GetIt.instance.get<SpecialtyDao>();
return dao.saveAllSpecialties(specialities);
}
}
initDbSync runs and inserts just fine. While db.specialtyDao.saveAllSpecialties(specialities); never actually exectues any SQL. I have it printing log statements for the moment so I can see what it's doing.
Update: I found out that VmDatabase.memory(logStatements: true); was needed to see the SQL. I can see it printing the statements.
I'm running on a simulator so I can look at the raw db file. And there's nothing there. When I query in the app there's also nothing there.
So what's not really clear in the documentation is that VmDatabase.memory(); opens up a new database in memory. Not takes the database from memory.
You want to take your reference to the file that you pass in the constructor, and use
VmDatabase(File(dbFile));
then it will actually run on your sql.

Resources