Flutter: Where to place sqflite code inside my application? - sqlite

I want to use sqflite in my app. To do this, I'm trying to follow this tutorial: https://flutter.dev/docs/cookbook/persistence/sqlite. However, I don't know where to place the code inside my application. In the tutorial, the code seems to be placed at the main() function - but, if I do that, how can I call the insert, update and delete methods at other files?
Update:
As suggested by #Madhavam Shahi, I created a file databaseServices.dart. Now, at the other file, I'm importing databaseServices.dart and trying to use it as below:
import 'databaseServices.dart';
DataBaseServices db=DataBaseServices();
db.delete() //example
However, it is not working. I think the databaseServices.dart is not structured the right way, but I can't spot the error. I know I must be making a very newbie mistake. Here is the code for the databaseServices.dart:
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'counter.dart';
class DatabaseServices {
void whatever() async {
// Open the database and store the reference.
final Future<Database> database = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await database;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await database;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
}

No, it doesn't matter wherever you create the database.
You can create a file databaseServices.dart which manages the database service. It'll be easy for you to manage your code then.
In the cookbook, they are just showing an example, how you can use sqlflite.
But, however you should place this line WidgetsFlutterBinding.ensureInitialized(); in your main() method before anything, IF you are doing an asynchronous task in the main() method.
Update:
To perform CRUD in other files,
import your databaseServices.dart file that file, in which you want to perform the CRUD.
import 'databaseServices.dart';
DataBaseServices db=DataBaseServices();// create an object (DataBaseServices is the name of the class)
//Now, you can access all the methods,
db.delete()//example
Alternatively, if you don't want to create a class in the databaseServices.dart file, and want to keep every function a top level function, then you can do the following.
import 'databaseServices.dart' as db;
//Now, you can access all the top level functions or variables.
db.delete()//example.
Update 2:-
To make database accessible to every function,
move Future database outside the whatever () method, and place it just below the class name. (Making it global so that every function can access it), notice i removed the "final" keyword, because we are going to initialise it later, in the whatever method. Now, in the whatever method do what you were, but instead of final Future database = //yoir code, do this, database =//your code.. by doing this, you will be initialising the database variable, and as database variable is a global variable(declared outside any function, inside a class), any function can access it. But you have to keep in mind that, database has to be initialised before you call any other method which requires database, because if you will not call whatever () method before any other function, then the database won't be initialised, and hence your other functions won't work.
Example,
import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'counter.dart';
class DatabaseServices {
Future<Database> database;//making database global so that every function inside the class can access it.
void whatever() async {
// Open the database and store the reference.
database = openDatabase(
// Set the path to the database.
join(await getDatabasesPath(), 'counter_database.db'),
// When the database is first created, create a table to store counters;
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
"CREATE TABLE counters(id INTEGER PRIMARY KEY, name TEXT, value INTEGER)",
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
}//Function whatever () ends here
// Define a function that inserts counters into the database.
Future<void> insertCounter(Counter counter) async {
// Get a reference to the database.
final Database db = await database;
// Insert the Counter into the correct table. Here, if a counter is inserted twice,
// it replace any previous data.
await db.insert(
'counters',
counter.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
// A method that retrieves all the counters from the counters table.
Future<List<Counter>> counters() async {
// Get a reference to the database.
final Database db = await database;
// Query the table for all the Counters.
final List<Map<String, dynamic>> maps = await db.query('counters');
// Counvert the List<Map<String, dynamic>> into a List<Counter>
return List.generate(maps.length, (i) {
return Counter(
id: maps[i]['id'],
name: maps[i]['name'],
value: maps[i]['value'],
);
});
}
// Method to update a Counter in the database
Future<void> updateCounter(Counter counter) async {
final db = await database;
await db.update(
'counters',
counter.toMap(),
where: "id = ?",
whereArgs: [counter.id],
);
}
//Delete a Counter from the database
Future<void> deleteCounter(int id) async {
final db = await database;
await db.delete(
'counters',
where: "id = ?",
whereArgs: [id],
);
}
}
Now, since there are no nested functions, you can easily create an object of the class, and call the functions as you need easily :)

Related

How to batch update arrays?

So I am creating an attendance app in which there is a list named attendees which contains the list of all students attending the lecture.
List<String> attendees = [uid0, uid1, uid2];
and the student data model is as follows
Student(
List<String> attendance = [at0, at1]; //this is the list of lectures attended
String name;
)
and the document name of every student is their user-id
How do I update the list of every student via the batch() function?
I assume you already have a list of attendees that contains user-id as you mentioned the userId would be the documentId in Firestore.
Therefore, you can do a loop and update your doc directly and then use commit to apply your changes.
updateBatch(List<String> attendees) async {
try {
final db = FirebaseFirestore.instance;
final WriteBatch batch = db.batch();
// based on this doc https://firebase.flutter.dev/docs/firestore/usage/#batch-write
// Each transaction or batch of writes can write to a maximum of 500 documents.
for (final userid in attendees) {
// Use the batch to update a document that exist otherwise it will throw error,
// in case you want to create that doc, maybe you should use `set` instead
// ref: https://pub.dev/documentation/cloud_firestore/latest/cloud_firestore/WriteBatch/update.html
batch.update(
//Give the ref of document.
db.document('Path/to/firestore/document'), // e.g: /Students/userId
//And the data to update.
{"key": value},
);
}
await batch.commit();
print('success');
} catch (e) {
print('error $e');
}
}
A few notes:
According to this doc, Each transaction or batch of writes can write to a maximum of 500 documents.
You can check out different WriteBatch methods doc here

Flutter and Firestore: How to update multiple specific documents from collection group

Okay, so I have multiple specific document ids that are all the same, but each one is used for different collections and purposes. Using a collection group as these documents are nested in subcollections, I was able to locate these documents and print them. How would I go about updating a specific field in all of these documents at the same time with some other data? In my case these documents despite being in different collections have a field called, plastics and I want to update them with int data.
Here is the code I used to retrieve these documents and filter to only print the one's I specificly need:
final String uid = FirebaseAuth.instance.currentUser.uid;
QuerySnapshot querySnapshot = await FirebaseFirestore
.instance
.collectionGroup("Members")
.get();
for (int i = 0; i < querySnapshot.docs.length; i++) {
var a = querySnapshot.docs[i];
if (a.id == uid) {
print(a.id);
}
}
Also here is some code I've used before for updating a field in a single document, but not all of them like I need to in this case.
Future<bool> addPlastic(String amount) async {
try {
String uid = auth.currentUser.uid;
var value = double.parse(amount);
DocumentReference documentReference =
FirebaseFirestore.instance.collection('some collection').doc(some document);
FirebaseFirestore.instance.runTransaction((transaction) async {
DocumentSnapshot snapshot = await transaction.get(documentReference);
if (!snapshot.exists) {
documentReference.set({'plastics': value});
return true;
}
double newAmount = snapshot.data()['plastics'] + value;
transaction.update(documentReference, {'plastics': newAmount});
return true;
});
} catch (e) {
return false;
}
}
You have two options, either loop through all the collections and sub collections (tedious), or store a list document references in one of the documents and change the data by looping through all of these document references ( better option). If you need some code or guidelines on how to do that, let me know.
EDIT
To use the document reference part, first, when creating the document, you have to do something like this
document(id).set({
"value1":"oko",
"plastic":"240",
//And the rest of your values
"otherSnapshots":[/*Your list of documentSnapshots with the same ID. Update it in this whenever you have new one by following the next snippet*/]
})
When you create a new document, navigate to this and add in the document Reference by
snapshot.reference.update({
'otherSnapshots':FieldValue.arrayUnion([/*Document Reference*/])
});
And next time, when you want to update all of them, use this field, loop through it and then you will get all the document references. I cannot create a code that you can directly copy paste into your code without seeing how u store data

Unable to Return Queried Data From Firebase (Flutter/Dart)

Context: I'm trying to query and return a String (imgUrl) from Firebase. I'm always able to print the string inside the query, but the returned value is always null. I'm wondering if my query is wrong and am not sure what best practices are.
Database Outline:
Query Function:
This is the code under our DatabaseService() class, which contains all database queries and updating functions.
String getImageUrl(String _uid) {
String _imgUrl;
Firestore.instance
.document('users/$_uid')
.get()
.then((value) => _imgUrl = value['imgUrl']);
return _imgUrl;
}
Main:
getImageUrl() is called under setImage(). The toast under setImage always returns null and so does the code under it.
String _uid;
// Sets variable '_uid' to the uid of the current user
// Gets called in initstate
Future _getUid() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
_uid = user.uid;
}
// Sets the profile photo. If there is no existing profile photo online,
// grab the image on the device. If there is no image online OR on the device,
// Display the default image
void setImage(String url) {
// Get the url that's stored in the db
String _tempUrl = DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}
#override
void initState() {
super.initState();
_getUid();
}
Please let me know what to do to fix this as it's driving me crazy. Thanks in advance.
Change the method to the following:
Future<String> getImageUrl(String _uid) async {
String _imgUrl;
DocumentSnapshot value =
await Firestore.instance.document('users/$_uid').get();
_imgUrl = value['imgUrl'];
return _imgUrl;
}
use async/await to wait for the future to finish, and then call it like the following:
void setImage(String url) async{
// Get the url that's stored in the db
String _tempUrl = await DatabaseService().getImageUrl(_uid); // always ends up being null
Fluttertoast.showToast(msg: "_tempUrl: $_tempUrl");
// Rest of the function
}

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.

Change feed not trigger with multiple insert document in Cosmos DB

I replicated the architecture below.
I insert the gps positions (document db) in cosmos db and in the javascript client (maps google) the pin moves.
All the step works: insert document db, trigger azure function and signalr that link client and document db in azure cosmos db.
The code to upload a document db in Cosmos:
Microsoft.Azure.Documents.Document doc = client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), estimatedPathDocument).Result.Resource;
ret[0] = doc.Id;
Azure function:
public static async Task Run(IReadOnlyList<Document> input, IAsyncCollector<SignalRMessage> signalRMessages, ILogger log)
{
if (input != null && input.Count > 0)
{
var val = input.Select((d) => new
{
genKey = d.GetPropertyValue<string>("genKey"),
dataType = d.GetPropertyValue<string>("dataType")
});
await signalRMessages.AddAsync(new SignalRMessage
{
UserId = val.First().genKey,
Target = "tripUpdated",
Arguments = new[] { input }
});
}
}
When I insert only one position the function in azure records the event and fire by moving the pin.
The problem is when I insert sequentially a series of positions in an almost instantaneous way and this does not trigger the function for the document following the first one.
Only if i insert a delay only some documents fire the trigger:
Microsoft.Azure.Documents.Document doc = client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), estimatedPathDocument).Result.Resource;
Thread.Sleep(3000);
ret[0] = doc.Id;
I don't know if I load the documents correctly, but even managing them in an asynchronous way (see under), it almost seems like the trigger is triggered only when the document in cosmos db is "really / physically" created.
Task.Run(async () => await AzureCosmosDB_class.MyDocumentAzureCosmosDB.CreateRealCoordDocumentIfNotExists_v1("axylog-cdb-01", "axylog-collection-01", realCoord, uri, key));
The solution can be to list the documents in a queue and load them on azure cosmos sequentially after a delay of about ten seconds one from the other?

Resources