Importing an SQFlite database from Flutter app's assets and using rawQuery to display specific rows - flutter

I've built an app using Flutter. Part of its functionality is that users can search through data which is in the assets area of the app. This data was originally in JSON format, although I have converted it into an SQLite database to save storage space. That has actually helped me to save around 90%, which is great. The problem is, the search delegate no longer works. It simply returns an empty list, although no errors are produced in the console.
I have created a model class to help read the data from the SQLite database table, which looks like this:
/// Class to handle the country data in the database
class CountriesDB {
/// Defining the variables to be pulled from the json file
late int id;
late String continent;
late String continentISO;
late String country;
late String countryISO;
late String flagIconLocation;
CountriesDB({
required this.id,
required this.continent,
required this.continentISO,
required this.country,
required this.countryISO,
required this.flagIconLocation,
});
CountriesDB.fromMap(dynamic obj) {
this.id = obj[id];
this.continent = obj[continent];
this.continentISO = obj[continentISO];
this.country = obj[country];
this.countryISO = obj[countryISO];
this.flagIconLocation = obj[flagIconLocation];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'id': id,
'continent': continent,
'continentISO': continentISO,
'country': country,
'countryISO': countryISO,
'flagIconLocation': flagIconLocation,
};
return map;
}
}
As far as I am aware, to read data in a database that is stored within the assets folder of the app, I need to programatically convert it into a working database. I have written the following code, to sort that:
/// Creating the database values
static final DatabaseClientData instance = DatabaseClientData._init();
static Database? _database;
DatabaseClientData._init();
/// Calling the database
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('databaseWorking.db');
return _database!;
}
/// Future function to open the database
Future<Database> _initDB(String filePath) async {
/// Getting the data from the database in 'assets'
var databasesPath = await getDatabasesPath();
var path = join(databasesPath, filePath);
/// Check if the database exists
var exists = await databaseExists(path);
if (!exists) {
/// Should happen only the first time the application is launched
print('Creating new copy from asset');
/// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
/// Copy from the asset
ByteData data =
await rootBundle.load('assets/data/database.db');
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
/// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print('Opening existing database');
}
return await openDatabase(path, readOnly: true);
}
The next thing I have done is to create a Future function that searches the database using a rawQuery. The code for this is:
/// Functions to search for specific database entries
/// Countries
static Future<List<CountriesDB>> searchCountries(String keyword) async {
final db = await instance.database;
List<Map<String, dynamic>> allCountries = await db.rawQuery(
'SELECT * FROM availableISOCountries WHERE continent=? OR continentISO=? OR country=? OR countryISO=?',
['%keyword%']);
List<CountriesDB> countries =
allCountries.map((country) => CountriesDB.fromMap(country)).toList();
return countries;
}
Finally, I am using the Flutter Search Delegate class to allow the user to interact with the database and search for specific rows. This is the widget I have built for that:
/// Checks to see if suggestions can be made and returns error if not
Widget buildSuggestions(BuildContext context) => Container(
color: Color(0xFFF7F7F7),
child: FutureBuilder<List<CountriesDB>>(
future: DatabaseClientData.searchCountries(query),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: PlatformCircularProgressIndicator(
material: (_, __) => MaterialProgressIndicatorData(
color: Color(0xFF287AD3),
),
cupertino: (_, __) => CupertinoProgressIndicatorData(),
));
default:
if (query.isEmpty) {
return buildAllSuggestionsNoSearch(snapshot.data!);
} else if (snapshot.hasError || snapshot.data!.isEmpty) {
return buildNoSuggestionsError(context);
} else {
return buildSuggestionsSuccess(snapshot.data!);
}
}
},
),
);
The idea is that the functionality I have built will return the whole list before a user searches and once a users starts typing, they will only be shown any rows that match their search query. This worked fine when I was using JSON data but it is returning an empty list, yet there are no errors printed in the console, at all. That makes it quite hard to know where my code is going wrong.
Where have I gone wrong with my code, such that it is not returning any data? How can I correct this? Thanks!

Related

how to retrive value from a firestore flutter where query

I started flutter recently, and I try to retrieve the data from a query I made using 'where' , but the only thing I got back is "Instance of '_JsonQueryDocumentSnapshot'".
I tried different thing , but nothing work or i do it badly
this is my code :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
I have also tried to use Future but without success :
class finduser extends StatelessWidget {
final String username;
finduser(this.username);
#override
Widget build(BuildContext context) {
CollectionReference users = FirebaseFirestore.instance.collection('users');
return FutureBuilder(
future: users.where('username', isEqualTo: '${username}').get(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
print("wrong");
return Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
print("doesnt exist");
return Text("User does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data! as Map<String, dynamic>;
print(snapshot.data!);
return Text("${data}");
}
return Text("loading");
},
);
}
}
for the moment, all usernames are just "username"
Thank you for the help
When you get your documents like this :
CollectionReference users =
FirebaseFirestore.instance.collection('users');
final documents =
await users.where("username", isEqualTo: "username").get();
documents.docs.forEach((element) {
print(element);
});
You are trying to print an instance of a QueryDocumentSnapshot
This QueryDocumentSnapshot has a method .data() which returns a Map<String,dynamic> aka JSON.
So in order to print the content of your Document, do this :
documents.docs.forEach((element) {
print(MyClass.fromJson(element.data()));
});
This data by itself will not be very useful so I recommend creating a factory method for your class :
class MyClass {
final String username;
const MyClass({required this.username});
factory MyClass.fromJson(Map<String, dynamic> json) =>
MyClass(username: json['username'] as String);
}
Now you can call MyClass.fromJson(element.data()); and get a new instance of your class this way.
I have searched a lot but i see you have written code right.
The only thing that came to my mind is that you didn't initialize your firebase to your flutter project (you should do it in any flutter project to be able to use flutter).
link of the document:
https://firebase.flutter.dev/docs/overview#initializing-flutterfire
In your first code snippet you are printing element, which are instances of the QueryDocumentSnapshot class. Since you're not accessing specific data of the document snapshot, you get its default toString implementation, which apparently just shows the class name.
A bit more meaningful be to print the document id:
documents.docs.forEach((doc) {
print(doc.id);
});
Or a field from the document, like the username:
documents.docs.forEach((doc) {
print(doc.get("username"));
});
Run this code, it will work.
I also faced this similar problem, so I used this work around.
Map<String, dynamic> data = {};
FirebaseFirestore.instance.collection('users').where("username", isEqualTo: "username").get().then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((value){
data = value.data()!;
print('printing uid ${data['uid']}');
print('printing username--${data['username']}');
print('printing all data--$data');
});
});

How to extract values from onCall firebase function and load them in future builder

i have a onCall cloud function which is returning
resp.status(200).send(JSON.stringify(entities));
In my flutter app, i have created this future to get values from it.
Future<void> dataDriven(String filename) async {
HttpsCallable callable =
FirebaseFunctions.instance.httpsCallable('fruitsType');
final results = await callable;
final datE = results.call(<String, dynamic>{
'filename': 'filename',
});
final dataF = await datE.then((value) => value.data);
print (dataF);
}
It is successfully printing the response which is as per expectation. but my snapshot is always returning null. It is not even reaching hasData stage. Please help.
Response;
[{"name":"banana","type":"fruit","count":0,"color":"yellow"},{{"name":"apple","type":"fruit","count":2,"color":"red"}]
FutureBuilder(
future: dataDriven('fruits.txt'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(
child: Text('An error has occurred!'),
);
} else {
final data = snapshot.data;
return Text(data.toString());
}
It looks like there are some issues that need to be fixed (See comments in code).
// Set the correct return type (not void because you are returning data)
Future<String> dataDriven(String filename) async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('fruitsType');
// You can just call the function here with await
final result = await callable.call({
// Remove the quotes on the filename value
'filename': filename,
});
// Don't forget to return the data
return result;
}
I suggest reading up on the documentation about calling cloud functions from a flutter app and basic dart syntax.

How to return a List, after a Method fills it, Flutter

I'm stuck with a problem and I wondered if you can help me.
I have a functions (in Flutter) that returns a List of Items. Now this List of Items should be Filled by an other function, which goes thought my Database and collect the right items. My Problem is, that my Function runs after the Return Statement... Here is some Code:
Future<List<MaterialItem>> getItems(String path, String fach) async {
// This is a empty List that I want to fill
List<MaterialItem> list = [];
// That's my Function, that fills the List
var result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
// Here the List gets filled
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
});
// Here the List should be returned, but after my Function fills it.
return list;
}
Hope you know what my problem is, and someone can help me.
I think you could solve this using a Completer. Your function should return the Future property of the Completer and the database call should then complete it.
Take a look at the API and the example:
https://api.dart.dev/stable/2.12.4/dart-async/Completer-class.html
For example: (pseudo code)
Future<List<MaterialItem>> getItems(String path, String fach) async {
// declare a completer
Completer<List<MaterialItem>> completer = Completer();
List<MaterialItem> list = [];
final result = await _db
.collection("$path/$fach/")
.get()
.then((QuerySnapshot querySnapshot) {
querySnapshot.docs.forEach((doc) {
list.add(MaterialItem.fromSnapshot(doc.data() as Map<String, dynamic>));
});
// let the database call complete the completer
completer.complete(list);
});
// return the future value of the completer
return completer.future;
}

Prepolulated SQLite database, on first launch is always empty - Flutter

Every time my app on her first launch or when the database is deleted, a copy of my prepopulated database is copied from the assets folder, after copying the app is restarted (if the database was deleted, but if it was the first time launching it will not restart) and this error always occurs:
RangeError (index): Invalid value: Valid value range is empty: 0
It happens because the widgets down on the widget tree need a list, but the database supposedly has no values on it.
But if I do a hot restart the database now has values and works normally
Part of the db_helper.dart responsible to copy the database and check if it already exists
Future startDB() async {
final dbDir = await getDatabasesPath();
final dbPath = join(dbDir, "visit_sps.db");
bool exist = await databaseExists(dbPath);
if (exist) {
print("db ja existe");
} else {
print("a criar copia dos assets");
try {
await Directory(dirname(dbPath)).create(recursive: true);
} catch (_) {}
ByteData data = await rootBundle.load("assets/database/visit_sps.db");
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await File(dbPath).writeAsBytes(bytes, flush: true);
print("db copiada");
}
openDatabase(dbPath);
}
My FutureBuilder and code to delete the database if the app was reinstalled and a older database is still present on the user device, using the Shared Preferences Package - pagina_principal.dart
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
DBHelper().startDB();
} else {
await prefs.setBool('seen', true);
DBHelper().deleteDB();
}
}
return FutureBuilder(
future: DBHelper().getLocais(), //Obter todos os Locais
builder: (context, AsyncSnapshot<List<Local>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
List<Local> locais = snapshot.data;
return Scaffold(
If I print the locais length is 0, but after hot restarting is now the correct number of elements on my database.
GetLocais()
Future<List<Local>> getLocais() async {
try {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('local');
return List.generate(
maps.length,
(i) {
return Local.fromMap(maps[i]);
},
);
} catch (ex) {
print(ex);
return new List<Local>();
}
}
What I think is the database is getting done being copied only after the getLocais() is done
I would really appreciate if someone could help, I am still new to Flutter ,if you didn't understand my explanation, I am sorry because English is not my main language!

Getting document from Firestore to pun inside a Custom User profile but it returns null. Dart/Flutter

Hi there I'm having some trouble to retrieve data from Firestore;
I created a class User for getting all the information for my app.
User Class:
class User {
String id;
String displayName;
String email;
String pictureURL;
String gender;
DateTime dateOfBirth;
User(fbauth.User user) {
id = user.uid;
displayName = user.displayName;
email = user.email;
pictureURL = user.photoURL;
// setting info
DocumentSnapshot resl = FirestoreUtil.read("AdditionalUserInfo", user.uid);
gender = resl.data()['gender'];
dateOfBirth = (resl.data()['date of birth'] as Timestamp).toDate();
}
FirestoreUtils class:
class FirestoreUtil {
static DocumentSnapshot read(String collection, String document) {
FirebaseFirestore.instance
.collection(collection)
.doc(document)
.get()
.then((DocumentSnapshot snapshot) {
if(snapshot.exists){
return snapshot;
}else{
print("no data found");
}
});
}
}
After I return the snapshot, it gets null. But if I do this it prints out the correct values:
class FirestoreUtil {
static DocumentSnapshot read(String collection, String document) {
FirebaseFirestore.instance
.collection(collection)
.doc(document)
.get()
.then((DocumentSnapshot snapshot) {
if(snapshot.exists){
print(snapshot.data);
}else{
print("no data found");
}
});
}
}
I tried literally everything but I couldn't figure out. Could someone help? I cannot imagine that is that hard to retrieve data inside a class.
It's probably Future(async/await) problem. You are returning value inside Future, you can get this value inside another then() etc.
class FirestoreUtil {
static DocumentSnapshot read(String collection, String document) async {
// You can directly return it too without assigning it to result variable
var result = await FirebaseFirestore.instance
.collection(collection)
.doc(document)
.get();
return result;
}
}
There is data() method for DocumentSnapshot, you can use result.data() and check if it's null, isEmpty etc.
I wrote this function with async/await but if you need Future and didn't want to wait operation to be finished, you can directly return
FirebaseFirestore.instance.collection(collection).doc(document).get();
and you can use then() after you get the Future.