Prepolulated SQLite database, on first launch is always empty - Flutter - 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!

Related

StreamBuilder doesn't updates UI when Firestore data changes

My goal:
I want to retrieve a list of documents from the Firebase Firestore using a Stream to update the interface in real time when the Firestore data changes.
The problem:
I am able to download the data from Firestore with the structure I need, but when I make changes in firestore the interface does not update in real time. When I reload the page, it updates, but that is not the behavior I need.
This is the Stream I have created:
Stream<DayModel> getInstances(String selectedDay, String userUid) async* {
DayModel retval = DayModel();
List<InstanceModel> instances = [];
int index = 0;
try {
final QuerySnapshot<Map<String, dynamic>> querySnapshot =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.where("instanceUsersUid", arrayContains: userUid)
.get();
instances = querySnapshot.docs
.map((instance) => InstanceModel.fromSnapshot(instance))
.toList();
for (InstanceModel instance in instances) {
final DocumentSnapshot<Map<String, dynamic>> instanceQuery =
await FirebaseFirestore.instance
.collection('instances')
.doc(selectedDay)
.collection('instancesUid')
.doc(instance.uid)
.get();
instance = InstanceModel.fromMap(instanceQuery);
instances[index] = instance;
index++;
}
retval.instances = instances;
yield retval;
} on Exception catch (e) {
print(e);
}
}
StreamBuilder code:
body: StreamBuilder<DayModel>(
stream:
OurDatabase().getInstances(selectedDay, _currentUser!.uid!),
builder:
(BuildContext context, AsyncSnapshot<DayModel> snapshot) {
if (snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Center(
child: snapshot.data!.instances!.isNotEmpty
? Text(snapshot.data!.instances![0].uid!)
: Text('No tienes instancias!'),
);
})
Maybe it's because I'm not returning the Stream with a QuerySnapshot?
I have read in other similar posts that it could be a problem with the keys, but I have tried several different combinations and it has not worked.
Do you have any idea what could be happening?
Thank you for your time.

shared_preferences values returning null in flutter

I am using shared_preferences to store a bool value locally but I think I am doing something wrong.
So first of all, here is my initState:
#override
initState(){
super.initState();
checkIfUserHasData();
getBoolValuesSF();
}
on checkIfUserHasData, Im calling another function at the end (addBoolToSF)
Future<void> checkIfUserHasData ()async {
var collection = FirebaseFirestore.instance.
collection('users').doc(userID).collection('personalInfo');
var querySnapshots = await collection.get();
for (var snapshot in querySnapshots.docs) {
documentID = snapshot.id;
}
await FirebaseFirestore.instance
.collection('users')
.doc(userID)
.collection('personalInfo').doc(documentID)
.get().then((value) {
if (!mounted) return;
setState(() {
gender = value.get('gender');
profileImageUrl = value.get('url');
print(profileImageUrl);
print(gender);
});
});
if (gender != null){
if (!mounted) return;
setState((){
isUserNew = false;
});
if(gender == "Male"){
setState(() => genderIsMale = true);
addBoolToSF();
}else{
setState(() => genderIsMale = false);
addBoolToSF();
}
}else {
return;
}
}
Then addBoolToSF:
addBoolToSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('genderType', genderIsMale);
}
Lastely getBoolValuesSF:
getBoolValuesSF() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
bool _genderType = ((prefs.getBool('genderType') ?? true)) ;
genderType = _genderType;
});
}
When the genderType value is obtained I then decide which image to be the background image on the screen:
CachedNetworkImage(
placeholder: (context, url) =>
CircularProgressIndicator(),
imageUrl: genderType ? // : //
With all of that said, here is what is happening when the gender is changed on the firebase firestore:
The first time I navigate or refresh the screen nothing is changed and I get this error:
type 'Null' is not a subtype of type 'bool'
The second time I refresh or navigate to the screen, I do get the correct image on place but I get the same error message again
type 'Null' is not a subtype of type 'bool'
I have tried several ways to solve this issue but i dont seem to get it right.
Edit: I have noticed that when I removed the last part for CachedNetworkImage, I get no error so I think the problem might be on this part
In case like that when you need to wait for a future to build some UI, the go to way is to use a FutureBuilder
You use it like this
FutureBuilder<bool>(
future: getBoolValuesSF,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
// build your UI here based on snapshot value
},
)
checkIfUserHasData() and getBoolValuesSF() both are future method. you can create another async method and put it inside initState.
#override
initState(){
super.initState();
newMthod();
}
newMthod() async{
await checkIfUserHasData();
await getBoolValuesSF();
}

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

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!

Flutter provider listeners not updating themselves when sqflite database data changes

I created a local database using flutter sqflite. And I want to listen to a length of a list of tasks on that database and update the total count of the tasks, when I add something or remove from that list. But when I call provider.of(context) thing, it doesn't update themselves, means it doesn't listen. I used a stream to grab the database data and show in the UI.
Here is the database class I created:
class TaskDatabase with ChangeNotifier {
final String dbName = 'db.sqlite';
Database? _db;
List<Task> _tasksList = [];
int _totalTaskCount = 0;
final _streamController = StreamController<List<Task>>.broadcast();
Stream<List<Task>> all() =>
_streamController.stream.map((tasks) => tasks..sort());
int get totalTasksCount {
return _totalTaskCount;
}
Future<bool> close() async {
final db = _db;
if (db == null) {
return false;
}
await db.close();
return true;
}
Future<bool> open() async {
if (_db != null) {
return true;
}
final directory = await getApplicationDocumentsDirectory();
final path = '${directory.path}/$dbName';
try {
final db = await openDatabase(path);
_db = db;
//creating the database table using sqflite
const createTable = '''CREATE TABLE IF NOT EXISTS "TABLEOFTASKS" (
"id" INTEGER NOT NULL,
"taskTitle" TEXT,
"isDone" INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY("id" AUTOINCREMENT));''';
await db.execute(createTable);
// read all existing task objects from the db
_tasksList = await _fetchTasks();
_streamController.add(_tasksList);
return true;
} catch (e) {
// print('error = $e');
return false;
}
}
// Creating a new task and save to the database:
// other CRUD functions are not added here:)
Future<bool> create(String taskTitle) async {
final db = _db;
if (db == null) {
return false;
}
try {
final id = await db.insert(
'TABLEOFTASKS',
{
'taskTitle': taskTitle,
'isDone': 0,
},
);
final task = Task(
id: id,
taskTitle: taskTitle,
isDone: false,
);
_tasksList.add(task);
_streamController.add(_tasksList);
_totalTaskCount = _tasksList.length;
notifyListeners();
return true;
} catch (e) {
print('error in creating task = $e');
return false;
}
}
}
Here is the widget that I want to listen and update:
final int taskCount = Provider.of<TaskDatabase>(context, listen: true).totalTasksCount;
.
.
.
Text(taskCount.toString()),
I added the provider at the top of the widget tree and there are no errors. Only thing happening is not updating the text widget
I created a streamBuilder and grabbed the list I want as a snapshot. Updating the list length using the provider package did not work. You can find in the DB class in the question to find how I created a stream of Tasks. Firest initialize the Database in init method.
late final TaskDatabase _crudStorage;
#override
void initState() {
_crudStorage = TaskDatabase();
_crudStorage.open();
super.initState();
}
#override
void dispose() {
_crudStorage.close();
super.dispose();
}
....
return Scaffold(
resizeToAvoidBottomInset: false,
drawer: const CustomDrawer(),
body: StreamBuilder(
stream: _crudStorage.all(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.active:
case ConnectionState.waiting:
if (snapshot.data == null) {
return const Center(child: Shimmer());
}
final tasksList = snapshot.data as List<Task>; /// The List I want
.
.
.
.
.
.
SomeTextWidget('The length of tasks = ${tasksList.length}'),

How to compare user input and the record in sqflite

In sqflite database, I have a table called user. The user consists of username a, b, c.
When user enter the input var a, the system will compare with the username in table user. If user input equal to username in table user, system will print the error message, else the system will print success.
I try to use the future builder, but it didnĀ“t work. How can I do the validation in Test.dart?
Thank you.
This is my code:
SqliteHelper.dart
Future getDat(UserAccount userAccount) async {
var dbClient = await db;
name = userAccount.username;
List<Map> result = await dbClient.query("UserAccount",
where: "username =?", whereArgs: [userAccount.username]);
if (result.length == 1) {
return name;
}
}
Test.dart
_saveData() async {
var db = UserAccountHelper();
var mynote = UserAccount(cTitle.text);
await db.getDat(mynote);
FutureBuilder(
future: db.getDat(mynote),
builder: (context, snapshot) {
if (snapshot.hasError) print(snapshot.error);
var data = snapshot.data;
if (snapshot.hasData) print("test 3");
if(snapshot.data.toString() == cTitle.text.toString())
{print("success")}
else{print("error")};
});
}
Future<String> getDat(String username) async {
var dbClient = await db;
List<Map> result = await dbClient.query("UserAccount",
where: "username =?", whereArgs: [username]);
if (result.length == 1) {
return name;
}
return null;
}
_saveData() async {
var db = UserAccountHelper();
var username = cTitle.text;
if(await db.getDat(username) != null) {
print('Error');
return;
}
print('Success');
}
Things I modified:
You don't need an UserAccount as parameter, because you use only the username from it.
The getDat returns null if the username isn't found in database;
In _saveData, if the result from the getDat is not null error is printed, else, success is printed.