How to save app data after restart of an app - flutter? - database

I was wondering how to make my app save data after restart? (The user can delete task and add new task to list, as well as check the box that the task is done. I want the app to save this data so when the user exists the app it will display all the tasks that he left the app with)
I was reading on google for few hours now, I got to
[1]: https://flutter.dev/docs/cookbook/persistence/reading-writing-files
This link as someone recommended on a similar post. But after reading it through I am a bit confused about where to start with my app.
Including some of my code and if you could help me I would really appreciate it as after hours of reading and watching tutorials I am still quite unsure where to start or which way of doing this is best.
My main.dart is this
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) =>
TaskData(), //changing builder: to create: fixed the errors i been having
child: MaterialApp(
home: TasksScreen(),
),
);
}
}
class TaskData extends ChangeNotifier {
List<Task> _tasks = [
Task(name: "Sample task 1"),
Task(name: "Sample task 2"),
Task(name: "Sample task 3"),
];
UnmodifiableListView<Task> get tasks {
return UnmodifiableListView(_tasks);
}
int get taskCount {
return _tasks.length;
}
void addTask(String newTaskTitle) {
final task = Task(name: newTaskTitle);
_tasks.add(task);
notifyListeners();
}
void updateTask(Task task) {
task.toggleDone();
notifyListeners();
}
void deleteTask(Task task) {
_tasks.remove(task);
notifyListeners();
}
Thank you so much!

The basic method is using the device local storage.
1 - Add the shared_preferences in your pubspec.yaml
2 - Create a class to write and read data :
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class StoreData {
StoreData._privateConstructor();
static final StoreData instance = StoreData._privateConstructor();
Future<void> saveString(String key, String value) async {
try{
SharedPreferences pref = await SharedPreferences.getInstance();
final encodedValue = base64.encode(utf8.encode(value));
pref.setString(key, encodedValue);
} catch (e){
print('saveString ${e.toString()}');
}
}
Future<String> getString(String key) async {
SharedPreferences pref = await SharedPreferences.getInstance();
final value = pref.getString(key) == null ? '' : pref.getString(key);
if (value.length > 0) {
final decodedValue = utf8.decode(base64.decode(value));
return decodedValue.toString();
}
return '';
}
Future<bool> remove(String key) async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.remove(key);
}
}
3 Use :
Save Data:
StoreData.instance.saveString('name', 'sergio');
Retrieve Data:
final String storedName = await StoreData.instance.getString('name');
print('The name is $storedName');
We have many other methods, like use a SQlite, NoSql or a Database in back-end, but the local storage is the most basic case

What you need is a database or a document based data storage. You can store data in a local sqlite db, using sqflite plugin. Or you can store in a JSON asset file.
You can also use a server or cloud service. Firebase is pretty well integrated with flutter, but AWS and Azure are also great.
You can write the data in a text file in the asset, but that would very complicated.

Related

How to use async functions to connect to database in Flutter?

I am trying to connect to a static database as it is explained in this answer. I therefore created an asynchronous function that looks like this:
Future<void> loadDataBase() async {
// Construct a file path to copy database to
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "asset_worldcities.db");
// Only copy if the database doesn't exist
if (FileSystemEntity.typeSync(path) == FileSystemEntityType.notFound) {
// Load database from asset and copy
ByteData data = await rootBundle.load(join('assets', 'worldcities.db'));
List<int> bytes = data.buffer.asUint8List(
data.offsetInBytes, data.lengthInBytes);
// Save copied asset to documents
await new File(path).writeAsBytes(bytes);
}
}
Now I thought I could access my database inside my main widget by using this function and then call
Directory appDocDir = await getApplicationDocumentsDirectory();
String databasePath = join(appDocDir.path, 'asset_database.db');
this.db = await openDatabase(databasePath);
initialized = true;
Future<List<Page>> search(String word, int parentId) async {
if (!initialized) await this._initialize();
String query = '''
SELECT * FROM users
LIMIT 25''';
return await this.db.rawQuery(query);
}
but this way I am not allowed to use this.db and also not await as I am not inside an async function. Where do I need to put this database request so that it works?
Depending whether you need to do this every time and the database could grow, or whether it's a one-time operation (which it seems like it might be?) and the database is small enough that it's not going to take long to query it, there are different approaches I'd take.
If it's a one-time per install sort of thing and the database will always be small, making the user wait while it copies the file across probably isn't a huge deal. In that case I'd do something like this:
main() async {
WidgetsFlutterBinding.ensureInitialized();
if (needToLoadDatabase()) {
await loadDatabase();
}
let users = await queryUsers();
runApp(MainWidget(users: users));
}
However, if you're reading from the database and it's something that could take any significant amount of time, I'd recommend initiating the load and then passing the future into your main widget, where it could use a FutureBuilder to build an intermediate UI.
That'd look something like this:
main() async {
WidgetsFlutterBinding.ensureInitialized();
let loadUsers = () async {
if (needToLoadDatabase()) {
await loadDatabase();
}
return await queryUsers();
}();
runApp(MainWidget(loadUsers: loadUsers));
}
class MainApp extends StatelessWidget {
final Future<Users> loadUsers;
MainApp({#required this.loadUsers, Key key}): super(key: key);
Widget build(BuildContext context) {
return FutureBuilder(
builder: (ctx, snapshot) {
if (snapshot.hasData) {
// build your UI with data
} else {
// build your UI without data
}
}
);
}
}
Also note that there's no reason you have to do the loading in the main function - you could make your widget stateful and kick that off in the initState, or any number of places like directly where you use the list. You could also look at the FutureProvider from the Provider package.

NoSQL creating and accessing different store references

I am trying to create a NoSQL database for a Flutter app that has different stores for different activities done throughout the day (sleep, exercise, eating, etc.). I don't want to hard code the stores and want to be able to add and delete stores as needed.
The issue I'm encountering is the intMapStoreFactory.store() requires a static input but the inputs for SembastActivityRepository cannot be static. Is there a way to create and access custom store names from outside this WembastActivityRepository class?
Thank you!
import 'package:get_it/get_it.dart';
import 'package:sembast/sembast.dart';
import './activity.dart';
import './activity_repository.dart';
class SembastActivityRepository extends ActivityRepository {
final activityName;
SembastActivityRepository({this.activityName});
final Database _database = GetIt.I.get();
final StoreRef _store = intMapStoreFactory.store(activityName);
#override
Future<int> insertActivity(Activity activity) async {
return await _store.add(_database, activity.toMap());
}
#override
Future updateActivity(Activity activity) async {
await _store.record(activity.id).update(_database, activity.toMap());
}
#override
Future deleteActivity(int activityId) async {
await _store.record(activityId).delete(_database);
}
#override
Future<List<Activity>> getAllActivities() async {
final snapshots = await _store.find(_database);
return snapshots
.map((snapshot) => Activity.fromMap(snapshot.key, snapshot.value))
.toList(growable: false);
}
}
I'm not sure what you mean by static but anyway your code could not compile and I strongly suggest using Strong mode to get compile time recommendation. A StoreRef is just a declaration of a store and its key and value type, you can create/and re-created a StoreRef at any time, the store itself is only created when you add records to it.
If it is just to to get your code to compile, initialize _store in the constructor:
class SembastActivityRepository {
final String activityName;
final StoreRef<int, Map<String, dynamic>> _store;
SembastActivityRepository({this.activityName})
: _store = intMapStoreFactory.store(activityName);
// ...
}

Initialize a store the first time is created in sembast

I'm developing a mobile application in Flutter, and I would like to manage the settings of my application (light theme or dark theme, ...).
I'm using sembast to store the settings of my application. I would like to initialize the store with some initial values the first time is created. How can I do that?
This is the my database helper class:
class AppDatabase {
// Name of the database.
static final String _dbName = 'mydb.db';
// Singleton instance.
static final AppDatabase _singleton = AppDatabase._();
// Singleton getter.
static AppDatabase get instance => _singleton;
// Transforms synchronous code into asynchronous code.
Completer<Database> _dbOpenCompleter;
// Private constructor.
AppDatabase._();
// Database object getter.
Future<Database> get database async {
// If completer is null, AppDatabaseClass is newly instantiated, so database is not yet opened.
if (_dbOpenCompleter == null) {
_dbOpenCompleter = Completer();
_openDatabase();
}
return _dbOpenCompleter.future;
}
Future<void> _openDatabase() async {
// Get a platform-specific directory where persistent app data can be stored.
final appDocumentDir = await getApplicationDocumentsDirectory();
// Path with the form: /platform-specific-directory/demo.db
final dbPath = join(appDocumentDir.path, _dbName);
final database = await databaseFactoryIo.openDatabase(dbPath);
// Any code awaiting the Completer's future will now start executing.
_dbOpenCompleter.complete(database);
}
}
Then I use the following repository to perform CRUD operations on my Settings:
class SettingsDatabaseRepository implements SettingsRepository {
// The name of the store.
static const String SETTINGS_STORE_NAME = 'settings';
// This store acts like a persistent map, values of which are Settings objects
// converted to Map.
final _settingsStore = intMapStoreFactory.store(SETTINGS_STORE_NAME);
// Private getter to shorten the amount of code needed to get the singleton
// instance of an opened database.
Future<Database> get _db async => await AppDatabase.instance.database;
#override
Future<void> insert(Settings settings) async {
await _settingsStore.add(await _db, settings.toMap());
}
#override
Future<void> update(Settings settings) async {
final finder = Finder(filter: Filter.byKey(settings.settingsId));
await _settingsStore.update(
await _db,
settings.toMap(),
finder: finder,
);
}
#override
Future<Settings> getSettings() async {
final recordSnapshots = await _settingsStore.find(await _db);
final settingsList = recordSnapshots.map((snapshot) {
final settings = Settings.fromMap(snapshot.value);
settings.copyWith(settingsId: snapshot.key);
return settings;
}).toList();
if (settingsList.isEmpty)
return null;
else
return settingsList.first;
}
}
I would like to initialize the store with some initial values the first time is created.
You cannot perform action when a store is created (since a store is not really created, it just holds records), however you can perform action when the database is created.
Sembast supports a database versioning system similar to sqlite, although here there is not much schema to modify. You can use this system to perform action when the database is created (or when you decide later in a new version to update it).
// Our shop store sample data
var store = intMapStoreFactory.store('shop');
var db = await factory.openDatabase(path, version: 1,
onVersionChanged: (db, oldVersion, newVersion) async {
// If the db does not exist, create some data
if (oldVersion == 0) {
await store.add(db, {'name': 'Lamp', 'price': 10});
await store.add(db, {'name': 'Chair', 'price': 15});
}
});
See more info

Firebase: Access to database.....but confused at onDataChange method

So i am curious when does onDataChange method occur?
It seems like it is activated when user add new information or change already existed data.
However, what I am trying to do is that, before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
so, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?
You basically set up a subscription to the "onDataChange" so its actually watching firebase for changes.
But for checking you could literate through the results or do one time query to the exact path your data it held at.
It also may be a better choice to record everything and then remove the data when not needed.
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { map } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private mysubscription: Subscription;
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase
) {
try {
//subscription using AngulaFire
this.mysubscription = this._DB.list("myFireBaseDataPath").snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
ngOnDestroy() {
this.mysubscription.unsubscribe();
}
//or we can do a one time query using just the firebase module
try {
return firebase.database().ref("myFireBaseDataPath").once('value').then(function(snapshot) { return snapshot.val(); })
.then(res => {
for (let myNode in res) {
console.log(res[myNode]);
console.warn(res[myNode].myChildPath);
console.log("----------------------------------);
}
})
.catch(error => console.log(error));
} catch (e) {
console.error(e);
}
//however it may be better practice to log all data and then firebase.database().ref(/logs").remove(); the entire log when not needed
var desc ="abc";
let newPostKey = firebase.database().ref("/logs").push();
newPostKey.set({
'info': desc,
'datetime': new Date().toISOString()
});
When does onDataChange method occur?
The onDataChange method is called for every change in the database reference it is attached to. It is also called for every visit to the database reference it is attached to.
For example,
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("some/database/refrence");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method will be fired for any change in the
database.getReference("some/database/refrence") part of the database.
// It will also be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
// This method will be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
and an error occurred
}
});
Before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
This can be done by calling the exists() method on the snapshot retrieved from your database query.
Check this stackoverflow question Checking if a particular value exists in the firebase database for an answer to that
So, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?
No. The onDataChange method is the callback used to retrieve data from the database. Even if you use the equalTo() method on a query, you'll still have to use the onDataChange method.
I am not a Firebaser Specialist tho. There are folks who work at Firebase on here. They could give you more information
PS: Please make your own research on your questions first before asking. Some questions are already answered in the documentation and on stackoverflow.

Google App Engine JDO Persistence Manager and 500 server error

I keep getting a 500 error everytime to run my service, since the google message tells mme almost nothing i began uploading the service and commenting parts out. It seems to work fine with out using the JDO persistence manager object. It would be great if someone can take a quick look at my code and tell me if I am using the JDO Persistence Manager wrong. This would help me alot.
#Path("/surveymakerpro/")
public class SurveyResource {
// JDO Persistence object
private PersistenceManager surveyDataStore = SurveyPersistenceManagerFactory
.get().getPersistenceManager();
#GET
#Produces({ "application/xml", "application/json" })
#Path("/survey/{surveyName}/")
public Survey getSurvey(#PathParam("surveyName") String surveyName) {
Survey e = surveyDataStore.getObjectById(Survey.class, surveyName);
return e;
}
#GET
#Produces({ "application/xml", "application/json" })
#Path("/allsurveys")
public SurveyNames getListOfSurveys() {
//test code
SurveyQuestion one = new SurveyQuestion();
one.setAnswer("Yes");
one.setId(3);
one.setQuestion("Do you like cake?");
SurveyQuestion two = new SurveyQuestion();
two.setAnswer("Yes");
two.setId(3);
two.setQuestion("Do you like cake?");
List<SurveyConverter> sur = new ArrayList<SurveyConverter>();
sur.add(new SurveyConverter(one));
sur.add(new SurveyConverter(two));
Survey surv = new Survey(sur);
surv.setSurveyName("Test Survey");
Survey survv = new Survey(sur);
survv.setSurveyName("sdd");
surveyDataStore.makePersistent(surv);
surveyDataStore.makePersistent(survv);
//test code
List<String> surveyNames = new ArrayList<String>();
Extent<Survey> extent = surveyDataStore.getExtent(Survey.class, false);
for (Survey survey : extent) {
surveyNames.add(survey.getSurveyName());
}
extent.closeAll();
SurveyNames surveyName = new SurveyNames(surveyNames);
surveyDataStore.close();
return surveyName;
}
#POST
#Consumes({ MediaType.APPLICATION_XML })
#Path("/addSurvey")
public void addSurvey(Survey survey) {
surveyDataStore.makePersistent(survey);
}
// This method gets a survey object stores its data into a TakenSurvey
// object and stores it
// in the Google data store.
#POST
#Consumes({ MediaType.APPLICATION_XML })
#Path("/addTakenSurvey")
public void addTakenSurvey(Survey survey) {
List<SurveyConverter> surveyConverter = survey.getSurvey();
TakenSurvey takenSurvey = new TakenSurvey();
for (SurveyConverter s : surveyConverter) {
TakenSurveyQuestion tsq = new TakenSurveyQuestion(s.getId(),
s.getQuestion(), s.getAnswer());
takenSurvey.getSurvey().add(tsq);
}
surveyDataStore.makePersistent(takenSurvey);
}
#GET
#Produces({ "application/xml", "application/json" })
#Path("/analysis/{surveyName}/{question}/")
public SurveyDataConverter analyzeSurveys(
#PathParam("surveyName") String surveyName,
#PathParam("question") String questionNum) {
Extent<TakenSurvey> extent = surveyDataStore.getExtent(
TakenSurvey.class, false);
List<TakenSurvey> takenSurveys = new ArrayList<TakenSurvey>();
for (TakenSurvey ts : extent) {
if (ts.getSurveyName().equals(surveyName)) {
takenSurveys.add(ts);
}
}
extent.closeAll();
SurveyAnalyzer analyze = new SurveyAnalyzer(takenSurveys);
analyze.getData();
SurveyData data = analyze.getSurveyData(Integer.parseInt(questionNum));
return new SurveyDataConverter(data);
}
// remove survey
// remove taken survey
}
Don't expect the error page to give you a stacktrace - that would be extremely bad practice for both security and usability reasons. Instead, exceptions are logged in the App Engine admin console, under 'logging'. The stacktrace there will tell you what's wrong; if you still can't figure it out, update your question to include it so we can help further.

Resources