Flutter provider listeners not updating themselves when sqflite database data changes - flutter

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}'),

Related

Integrate Provider with SharedPreferences to save and get Provider data

The list which stores the task data is integrated with provider for state management, but once I close the app and reopen it again, all tasks vanish.
With resources, I got to know about SharedPreferences.
How do I go about saving and getting the data using shared preferences. I have given the code a try, but does not seem to work in my favor.
void saveData() async {
final prefs = await SharedPreferences.getInstance();
final String encodedData = Task.encode(tasks);
await prefs.setString('task_data', encodedData);
}
void getData() async {
final prefs = await SharedPreferences.getInstance();
final String taskString = prefs.getString('task_data').toString();
List<Task> tasksData = Task.decode(taskString);
_tasks = tasksData;
}
encode() and decode() functions help in mapping List to String and String to List respectively.
static String encode(List<Task> tasks) {
return jsonEncode(
tasks.map<Map<String, dynamic>>((task) => Task.toMap(task)).toList(),
);
}
static List<Task> decode(String tasks) {
var data = (jsonDecode(tasks) as List<dynamic>?);
if (data != null) {
return (jsonDecode(tasks) as List<dynamic>?)!.map<Task>((task) {
return Task.fromJson(task);
}).toList();
} else {
return <Task>[];
}
}
The Task list in displayed using ListView.
Widget build(BuildContext context) {
return Consumer<TaskData>(
builder: (context, taskData, child) {
taskData.getData();
return ListView.builder(
itemCount: taskData.taskCount,
itemBuilder: (context, index) {
taskData.sortTaskList();
final task = taskData.tasks[index];
return TaskTile(
taskTitle: task.name,
isChecked: task.isDone,
checkboxCallBack: (checkBoxState) async {
taskData.upDateTask(task);
taskData.saveData();
},
longPressCallBack: () async {
taskData.removeTask(task);
taskData.saveData();
},
);
},
);
},
);
}
I am expecting that you're using ChangeNotifier with Provider package in TaskData class.
In this case you have to add notifyListener() inside getData() because it is async task and you are updating values.
Future<void> getData() async {
final prefs = await SharedPreferences.getInstance();
final String taskString = prefs.getString('task_data').toString();
List<Task> tasksData = Task.decode(taskString);
_tasks = tasksData;
notifyListener(); // Add this line
}

Riverpod future provider not rebuilding ui

My problem is that when I run the app, the data doesn't show up on the UI. The code below is rendered under a bottom navigation bar format which is a stateful widget. To my knowledge the below code should work (show data on the initial running of app).
The code works but the data is only shown when I press hot reload. I've tried everything that I know but it still doesn't show data when I start the app.
final imageControllerProvider = Provider((ref) {
return ImageController();
});
final mainScreenImages = FutureProvider<List<String>>((ref) async {
List<String> list = [];
list = await ref.watch(imageControllerProvider).getImages();
return list;
});
class ImageController{
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
reference.listAll().then((value) {
for (var element in value.items) {
element.getDownloadURL().then((e) => imageUrls.add(e));
}
});
} catch (e) {
print(e);
}
return imageUrls;
}
}
class GenerateImages extends ConsumerWidget {
const GenerateImages({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final imageList = ref.watch(mainScreenImages);
final double screenwidth = MediaQuery.of(context).size.width;
final double screenheight = MediaQuery.of(context).size.height;
return imageList.when(data: (data) {
return Text('$data');
}, error: (_, __) {
return const Scaffold(
body: Center(
child: Text("OOPS"),
),
);
}, loading: () {
return const Center(child: const CircularProgressIndicator());
});
}
}
I think the problem is because in getImages() you are not awaiting the results instead you are using the then() handler to register callbacks. Replace your getImages() function with this and try.
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
final value = await reference.listAll();
for (var element in value.items) {
final url = await element.getDownloadURL();
imageUrls.add(url);
}
} catch (e) {
print(e);
}
return imageUrls;
}
}

Singleton doesn't have unique instance Flutter

I'm trying to implement the singleton pattern with null safety in Flutter, to do a unique instance for my sqflite database, but after initialization in the splashScreen, when I try to access it in another Widget, it seems that it's not the same instance since the database is not initialized. I tried to add a random() int to check if it has the same value in the different widget, and the value change each times I request it.
My code is like this :
class DatabaseHandler {
final int random = new Random().nextInt(200);
static final DatabaseHandler instance = new DatabaseHandler._internal();
factory DatabaseHandler() {
return instance;
}
DatabaseHandler._internal();
late Database _database;
Database getDb() {
print(random);
return _database;
}
Future<void> initDB() async {
var path = await getDatabasesPath();
var dbPath = join(path, 'test.db');
Database dbConnection = await openDatabase(dbPath, version: 1,
onCreate: (Database db, int version) async {
return db.execute(
"CREATE TABLE favorite_page(id TEXT PRIMARY KEY, isFavorite BOOL)",
);
});
this._database = dbConnection;
}
}
I got a "Not initialize exception" when I do getDb() even if I used initDB() in the splashScreen.
I call the singleton in two places :
The db is initialized here :
class _SplashPageState extends State<SplashPage> {
static const String route = "/splash";
void initializeFlutterFire() async {
// Wait for Firebase to initialize and set `_initialized` state to true
await Firebase.initializeApp();
PushNotificationService pushNotif = PushNotificationService();
pushNotif.initialise();
}
#override
initState() {
super.initState();
WidgetsFlutterBinding.ensureInitialized();
initializeFlutterFire();
DatabaseHandler().initDB();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Lottie.asset('assets/lotties/loading-screen.json',
animate: true, repeat: true)));
}
}
and used here :
class PagesDb {
Future<FavoritePage> insert(FavoritePage page) async {
var db = DatabaseHandler().getDb();
await db.insert(tablePage, page.toMap());
return page;
}
}
But I tried to just use DatabaseHandler().getDb() in different widgets, they got the same random value, but not the same as in the splash screen

Flutter - Widget that no longer appears in the widget tree or this error might indicate a memory leak Warning

I have 3 page I check transitions with bottomNavigationBar first page is Soclose in this page im gettting information from the database and print it on the screen.
I'm getting information from the database smoothly but when i switch screens my console gives warning messages. An error appears in the console, but the application is working properly. When changing screens and returning to the old page(Soclose page), an error page appears and disappears within milliseconds.
I cant find similar questions and i tried to make suggestions in the warnings but either I couldn't do it or the solutions don't work.
Related soclose dart file:
class _Closesevents extends State<Soclose> {
List<Event> eventList;
int eventListLen;
#override
void initState() {
try{
final Future<Database> dbFuture = DbHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Event>> eventListFuture = DbHelper().getEventList();
eventListFuture.then((eventList) {
setState(() {
this.eventList = eventList;
this.eventListLen = eventList.length;
});
});
});}
catch (e,s)
{
print("[ERROR] $e");
print("[ERROR TREE]\n$s");
}
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
child: new ListView.builder(
itemCount: eventListLen,
itemBuilder: (BuildContext context, int index) =>
buildTripCard(context, index)),
);
}
Widget buildTripCard(BuildContext context, int index)
...
Databasehelper file
import ...
class DbHelper {
static DbHelper _databaseHelper; // Singleton DatabaseHelper
static Database _database;
static final String _tablename = EventConstants.TABLE_NAME;
static final String _columnId = EventConstants.COLUMN_ID;
static final String _columnTitle = EventConstants.COLUMN_TITLE;
static final String _columnDate = EventConstants.COLUMN_DATE;
static final String _columnStartTime = EventConstants.COLUMN_STARTTIME;
static final String _columnFinishTime = EventConstants.COLUMUN_FINISHTIME;
static final String _columnDesc = EventConstants.COLUMN_DESCRIPTION;
static final String _columnIsActive = EventConstants.COLUMN_ISACTIVE;
DbHelper._createInstance(); // Named constructor to create instance of DatabaseHelper
factory DbHelper() {
if (_databaseHelper == null) {
_databaseHelper = DbHelper._createInstance(); // This is executed only once, singleton object
}
return _databaseHelper;
}
Future<Database> get database async {
if (_database == null) {
_database = await initializeDatabase();
}
return _database;
}
static Future<Database> initializeDatabase() async {
Directory directory = await getApplicationDocumentsDirectory();
String path = directory.path + 'takvimapp.db';
// Open/create the database at a given path
var notesDatabase = await openDatabase(path, version: 1, onCreate: _createDb);
return notesDatabase;
}
static void _createDb(Database db, int newVersion) async {
await db.execute('CREATE TABLE $_tablename ( $_columnId INTEGER PRIMARY KEY NOT NULL,$_columnTitle TEXT ,$_columnDate TEXT,$_columnStartTime TEXT,$_columnFinishTime TEXT,$_columnDesc TEXT,$_columnIsActive INTEGER);');
}
// Get all events --map
Future<List<Map<String, dynamic>>> getEventMapList() async {
Database db = await this.database;
var result = await db.query(_tablename, orderBy: '$_columnTitle ASC');
return result;
}
// Insert Operation: Insert a Event object to database
Future<int> insertEvent(Event event) async {
Database db = await this.database;
var result = await db.insert(_tablename, event.toMap());
return result;
}
// Update Operation: Update a Event object and save it to database
Future<int> updateEvent(Event event) async {
var db = await this.database;
var result = await db.update(_tablename, event.toMap(), where: '$_columnId = ?', whereArgs: [event.id]);
return result;
}
// Delete Operation: Delete a Event object from database
Future<int> deleteEvent(int id) async {
var db = await this.database;
int result = await db.rawDelete('DELETE FROM $_tablename WHERE $_columnId = $id');
return result;
}
// Get number of Event objects in database
Future<int> getCount() async {
Database db = await this.database;
List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT (*) from $_tablename');
int result = Sqflite.firstIntValue(x);
return result;
}
// Convert map to list
Future<List<Event>> getEventList() async {
var eventMapList = await getEventMapList(); // Get 'Map List' from database
int count = eventMapList.length; // Count the number of map entries in db table
List<Event> eventList = List<Event>();
// For loop to create a 'Event List' from a 'Event List'
for (int i = 0; i < count; i++) {
eventList.add(Event.fromMap(eventMapList[i]));
}
return eventList;
}
static Future closeDb() => _database.close();
}
The error warning is constantly written to the console in an infinite loop.
To get rid of the warning, I need to close the app and restart the emulator.
Warning message:
E/flutter (30455): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: setState() >called after dispose(): _CountDownItemState#2bbc3(lifecycle state: defunct, not mounted)
E/flutter (30455): This error happens if you call setState() on a State object for a widget that no >longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its >build). This error can occur when code calls setState() from a timer or an animation callback.
E/flutter (30455): The preferred solution is to cancel the timer or stop listening to the animation >in the dispose() callback.
Another solution is to check the "mounted" property of this object >before calling setState() to ensure the object is still in the tree.
E/flutter (30455): This error might indicate a memory leak if setState() is being called because >another object is retaining a reference to this State object after it has been removed from the >tree. To avoid memory leaks, consider breaking the reference to this object during dispose().
Solution:
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _db.getEventList(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Text("Loading....."),
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(snapshot.data[index].title),
);
});
}
}),
);
}
The issue is with your initState function override. It's good practice to also call the super of initState, super.initState, before all other logic. Your Futures may be completing too quickly, and calling setState before the state is even initialized. Simply move super.initState(); as the first statement in the override. Ex.
#override
void initState() {
super.initState();//Always call this first
try{
final Future<Database> dbFuture = DbHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Event>> eventListFuture = DbHelper().getEventList();
eventListFuture.then((eventList) {
setState(() {
this.eventList = eventList;
this.eventListLen = eventList.length;
});
});
});}
catch (e,s)
{
print("[ERROR] $e");
print("[ERROR TREE]\n$s");
}
}
Edit: However, this can still lead to errors as setState could still be called before the widget is mounted. This is why the FutureBuilder widget exists. Wrap the widget that needs this Future data in your build method, pass the Future to the future parameter of the FutureBuilder and access the data with the AsyncSnapshot that the builder provides. See more about FutureBuilder.

Flutter : SQFLITE Insert Image from API

I'm working with Flutter SQFlite.
I have App News and in this App have feature read News Offline, So i go to with SQFlite.
I have been to Insert and Fetch data from SQFlite , But the problem is Image what I insert from API , so if i don't have connection i can't fetch the Image.
I'm Thinking about firstly download Image from API , then save into directory device and calling Image from directory based on ID News.
Can you help me with this case ?
Thank's
My Full Source Code
import 'package:flutter_news/model/Bookmark_model.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
class DBProvider {
DBProvider._();
static final DBProvider db = DBProvider._();
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
_database = await initDB();
return _database;
}
initDB() async {
Directory documentDirectory = await getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, "flutter_news.db");
return await openDatabase(path,
version: 1, onCreate: _onCreate, onUpgrade: _onUpgrade);
}
void _onUpgrade(Database db, int oldVersion, int newVersion) {
//Run Migration according database versions
}
void _onCreate(Database db, int version) async {
var sqlCreateTable = '''CREATE TABLE bookmark(
id_berita TEXT,
judul_berita TEXT,
isi_berita TEXT,
gambar_berita TEXT,
nama_kategori TEXT,
nama_wartawan TEXT,
tanggal_berita TEXT
)''';
await db.execute(sqlCreateTable);
print("Table Bookmark Has Been Created");
}
// Future newBookmark(Bookmark newBookmark) async {
// final db = await database;
// var res = await db.rawInsert(
// "INSERT INTO tb_bookmark (id_berita,judul_berita,isi_berita,nama_kategori,nama_wartawan,tanggal_berita)VALUES(\'${newBookmark.idBerita}\',\'${newBookmark.judulBerita}\',\'${newBookmark.isiBerita}\',\'${newBookmark.namaKategori}\',\'${newBookmark.namaWartawan}\',\'${newBookmark.tanggalBerita}\')");
// print("Print : $res");
// return res;
// }
Future addBookmark(Bookmark newBookmark) async {
print(newBookmark);
var db = await database;
int res = await db.insert("bookmark", newBookmark.toJson());
print('Bookmark has been Added $res');
return res;
}
Future<List<Bookmark>> getAllBookmark() async {
final db = await database;
var res = await db.query('bookmark');
List<Bookmark> list = res.isNotEmpty
? res.map((json) => Bookmark.fromJson(json)).toList()
: [];
return list;
}
}
My Code Fetch Data
return Container(
child: FutureBuilder(
future: DBProvider.db.getAllBookmark(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
return LoadingIndicator();
break;
default:
if (!snapshot.hasData) {
return DataNotFound(
iconData: Icons.sentiment_dissatisfied,
sizeIcon: 100,
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return Text('${snapshot.data[index].gambarBerita}');
},
);
}
}
},
),
);