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

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.

Related

LateInitializationError: Field 'check' has not been initialized

I'm trying to Use data that I fetched from database and i got an error : "LateInitializationError: Field 'check' has not been initialized. "
, i tried to remove the late word and adding " ? " and it gives another error "Expected a value of type 'num', but got one of type 'Null'
"
class _letterssState extends State<letterss> {
late var check;
Future getData() async{
var url = 'http://ip/getSpell.php';
http.Response response = await http.get(Uri.parse(url));
var data = jsonDecode(response.body);
check=data;
print(data.toString());
}
bool searchRes (String s){
int x=0;
for ( var i=0 ; i<check.length;i++ )
{
if (check[i]['letter']==s){
x=i;
}
}
if (check[x]['result']=='true')
{
return true;
}
else
{
return true;
}
}
initState()
{
getData();
}
It will take some frame to get data from getData future method and assigning on check.
It would better to use FutureBuilder for future methods. Follow this doc example
Future<List<yourDataType>?> getData() async {
var url = 'http://ip/getSpell.php';
http.Response response = await http.get(Uri.parse(url));
var data = jsonDecode(response.body);
return data;
}
late final future = getData();
#override
Widget build(BuildContext context) {
return FutureBuilder<List<YourDataType>?>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
//todo:
}
return CircularProgressIndicator();
},
);
}

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
}

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

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 throws error when context is passed to a Provider from the init method

I have a MainScreen (stateful) with the following method:
#override
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) async {
loadFirebaseUser(context);
});
}
My 'loadFirebaseUser' method is in another file called Constants.dart which can be used from any screen.
The function is as follows:
Future<bool> loadFirebaseUser(BuildContext context) async {
Auth _auth = Auth();
FirebaseUser cUser = await _auth.currentUser();
if (cUser.isEmailVerified) {
DocumentSnapshot snapshot = await Firestore.instance
.collection('Profile')
.document(cUser.uid)
.get();
if (snapshot.data != null) {
User user = Provider.of<UserData>(context).getUser();
user = User.fromMap(snapshot);
Provider.of<UserData>(context).setUser(user);
return true;
} else {
return false;
}
}
return false;
}
I am getting the follwing error when this code is executed:
Unhandled Exception: Looking up a deactivated widget's ancestor is unsafe.
Needless to say that as result of this the user is not getting loaded. The error points to the following line:
User user = Provider.of<UserData>(context).getUser();
I want the 'loadFirebaseUSer' function to execute automatically and not on any button click, so this is the only place I know to place this code. Is there a way to achieve this differently? Thanks
Did you tried to get context this way?
Future.delayed(Duration.zero, () {
var myState = Provider.of<MyState>(context);
.......
});