I am creating a note taking app. A not should be added when the screen is popped. The Note is being stored in Database only once but a duplicates are being added to stream when hotrestarted the duplicate automatically goes have been trying to solve this error for quite alot of time but to no benfite. I have provided two codes add Note Screen. Which is the screen through which I add notes. NotesService contains the logic of how I add code
Add Note Screen
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mynotes/constants/colors/app_colors.dart';
import 'package:mynotes/constants/widgets/helper_text.dart';
import 'package:mynotes/services/auth/auth_services.dart';
import 'dart:developer' as devtools show log;
import 'package:mynotes/services/crud/notes_services.dart';
import 'package:mynotes/utils/show_loading_dialog.dart';
class AddNoteView extends StatefulWidget {
const AddNoteView({super.key});
@override
State<AddNoteView> createState() => _AddNoteViewState();
}
class _AddNoteViewState extends State<AddNoteView> {
DatabaseNote? _note;
late final NotesService _notesService;
late FocusNode _titleFocus;
late FocusNode _textFocus;
late TextEditingController _titleController;
late TextEditingController _textController;
late final Future<DatabaseNote> _noteFuture;
void _deleteNoteIfTextIsEmpty() {
final note = _note;
if (note != null &&
_textController.text.isEmpty &&
_titleController.text.isEmpty) {
_notesService.deleteNote(noteId: note.id);
devtools.log('deleted Note because Empty');
}
}
void _saveNoteIfTextIfTextIsNotEmpty() async {
final note = _note;
if (note != null && _textController.text.isNotEmpty ||
_titleController.text.isNotEmpty) {
final text = '${_titleController.text}&${_textController.text}';
await _notesService.updateNote(note: note!, text: text);
}
}
Future<DatabaseNote> createNewNote() async {
final existingNote = _note;
if (existingNote != null) {
return existingNote;
}
final currentUser = AuthServices.firebase().currentUser!;
final email = currentUser.email!;
final owner = await _notesService.getUser(email: email);
devtools.log("Created note: ${owner.id} Add Note View file");
return await _notesService.createNote(
owner: owner,
text: '',
);
}
@override
void initState() {
super.initState();
_notesService = NotesService.singleton();
_titleFocus = FocusNode();
_textFocus = FocusNode();
_titleController = TextEditingController();
_textController = TextEditingController();
_noteFuture = createNewNote();
Future.delayed(Duration(milliseconds: 300), () {
if (context.mounted) FocusScope.of(context).requestFocus(_titleFocus);
});
}
@override
void dispose() {
_deleteNoteIfTextIsEmpty();
_saveNoteIfTextIfTextIsNotEmpty();
_titleFocus.dispose();
_textFocus.dispose();
_titleController.dispose();
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.softWhite,
appBar: AppBar(
backgroundColor: AppColors.softWhite,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
automaticallyImplyLeading: false,
title: Transform.translate(
offset: Offset(-16, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Stack(
children: [
Icon(
Icons.arrow_back_ios_new_rounded,
size: 26,
color: AppColors.jetBlack,
),
Positioned(
left: 1,
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 26,
color: AppColors.jetBlack,
),
),
],
),
),
HelperText(
text: 'Notes',
textColor: AppColors.jetBlack,
fontSize: 18,
fontWeight: FontWeight.w600,
),
],
),
),
actions: [
IconButton(
icon:
Icon(Icons.favorite_border_rounded, color: AppColors.jetBlack),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.save_alt_rounded, color: AppColors.jetBlack),
onPressed: () {},
),
],
),
body: FutureBuilder(
future: _noteFuture,
builder: (conext, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
_note = snapshot.data as DatabaseNote;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _titleController,
focusNode: _titleFocus,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.multiline,
onSubmitted: (_) =>
FocusScope.of(context).requestFocus(_textFocus),
style: GoogleFonts.lobster(
fontSize: 34,
// Larger size for title
fontWeight: FontWeight.bold,
color: AppColors.jetBlack,
),
decoration: InputDecoration(
hintText: "Title",
hintStyle: TextStyle(
fontSize: 34,
fontWeight: FontWeight.normal,
color:
Colors.grey.shade400, // Light grey hint color
),
border: InputBorder.none,
),
minLines: 1,
maxLines: 2, // Limit to two lines like Apple's Notes
),
// Spacing between title and body
Expanded(
child: TextField(
controller: _textController,
focusNode: _textFocus,
textInputAction: TextInputAction.newline,
style: TextStyle(
fontSize: 18,
color: AppColors.jetBlack,
height: 1.5, // Increase line height for readability
),
decoration: InputDecoration(
hintText: "Write your note here...",
hintStyle: TextStyle(
fontSize: 18,
color:
Colors.grey.shade400, // Light grey hint color
),
border: InputBorder.none,
),
minLines: 1,
maxLines: null,
keyboardType: TextInputType.multiline,
),
),
],
),
);
default:
return showLoadingDialog();
}
}),
);
}
}
NotesService
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:mynotes/services/crud/crud_exceptions.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' show join;
import 'dart:developer' as devtools show log;
class NotesService {
Database? _db;
List<DatabaseNote> _notes = [];
late final StreamController<List<DatabaseNote>> _notesStreamController;
//Here we are declaring a private constructor only
NotesService._sharedInstance() {
_notesStreamController = StreamController<List<DatabaseNote>>.broadcast(
onListen: () => _notesStreamController.sink.add(_notes));
}
static final NotesService _shared = NotesService._sharedInstance();
factory NotesService.singleton() => _shared;
//broadCastStream Mean It can be listened more than once;
Stream<List<DatabaseNote>> get allNotes => _notesStreamController.stream;
Future<DatabaseUser> getOrCreateUser({required String email}) async {
await ensureDbIsOpen();
try {
final user = await getUser(email: email);
return user;
} on CouldNotFindUser {
final createdUser = await createUser(email: email);
return createdUser;
} catch (e) {
rethrow;
}
}
Future<void> _cacheNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
devtools.log(_notes.toString());
_notesStreamController.add(_notes);
}
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
final db = _getDatabaseOrThrow();
//Id there is no note an exception will be thrown
await getNote(id: note.id);
final updateCount = await db.update(noteTable, {
textColumn: text,
isSyncedWithCloudColumn: 0,
});
if (updateCount == 0) {
throw CouldNotOpenNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere(
(element) => element.id == updatedNote.id,
);
devtools.log('Note is removed before updaing not shoulf == ""');
_notes.add(await getNote(id: note.id));
devtools.log(
'Before updating stream: ${_notes.length} updateNote NotesServices file');
_notesStreamController.add(_notes);
devtools.log(
'After updating stream: ${_notes.length}Update Note Note Services file');
return updatedNote;
}
}
Future<List<DatabaseNote>> getAllNotes() async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(noteTable);
devtools.log(notes.toString());
return notes.map((note) => DatabaseNote.fromRow(note)).toList();
}
Future<DatabaseNote> getNote({
required int id,
}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(
noteTable,
where: 'id = ?',
whereArgs: [id],
);
if (notes.isEmpty) {
throw CouldNotFindNote();
} else {
final note = DatabaseNote.fromRow(notes.first);
_notes.removeWhere((note) => note.id == id);
_notes.add(note);
_notesStreamController.add(_notes);
return DatabaseNote.fromRow(notes.first);
}
}
Future<int> deleteAllUsers() async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
return await db.delete(userTable);
}
Future<int> deleteAllNotes({required int userId}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
return await db.delete(noteTable);
}
Future<void> deleteNote({required int noteId}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
noteTable,
where: 'id = ?',
whereArgs: [noteId],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
}
_notes.removeWhere(
(element) => element.id == noteId,
);
_notesStreamController.add(_notes);
}
Future<DatabaseNote> createNote({
required DatabaseUser owner,
required String text,
}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final DatabaseUser dbUser = await getUser(email: owner.email);
if (dbUser != owner) {
throw CouldNotFindUser();
}
//make sure owner has the same id which is stored in the database;
final noteId = await db.insert(noteTable, {
userIdColumn: owner.id,
textColumn: text,
isSyncedWithCloudColumn: 1,
});
final note = DatabaseNote(
id: noteId,
userId: owner.id,
text: text,
isSyncedWithCloud: true,
);
devtools.log("Created note: ${owner.id} Notes Services file");
_notes.add(note);
devtools.log(
'Before updating stream: ${_notes.length} createNoteFunction notesServices file');
_notesStreamController.add(_notes);
devtools.log(
'After updating stream: ${_notes.length} create Note function Notes Services file');
return note;
}
Future<DatabaseUser> getUser({required String email}) async {
final db = _getDatabaseOrThrow();
final user = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (user.isEmpty) {
throw CouldNotFindUser();
} else {
return DatabaseUser.fromRow(user.first);
}
}
Future<DatabaseUser> createUser({required String email}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final rusults = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (rusults.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
return DatabaseUser(id: userId, email: email);
}
Future<void> deleteUser({required String email}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final int deletedCount =
await db.delete(userTable, where: 'email = ?', whereArgs: [
email.toLowerCase(),
]);
if (deletedCount < 1) {
throw CouldNotDeleteUser();
}
}
Database _getDatabaseOrThrow() {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}
Future<void> close() async {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}
//Insuring Database is not being opened again and again and also to open database automatically when using these functions. other eg delte user, create user,
Future<void> ensureDbIsOpen() async {
try {
await open();
} on DatabaseAlreadyOpenException {
//
}
}
//It will open and create database
Future<void> open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException();
}
try {
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath);
_db = db;
// create the user table
await db.execute(createUserTable);
// create note table
await db.execute(createNoteTable);
await _cacheNotes();
} on MissingPlatformDirectoryException {
throw UnableToGetDocumentsDirectory();
}
}
}
@immutable
class DatabaseUser {
final int id;
final String email;
const DatabaseUser({
required this.id,
required this.email,
});
DatabaseUser.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
email = map[emailColumn] as String;
@override
String toString() => 'Person, ID = $id, email = $email';
@override
bool operator ==(covariant DatabaseUser other) => id == other.id;
@override
int get hashCode => id.hashCode;
}
class DatabaseNote {
final int id;
final int userId;
final String text;
final bool isSyncedWithCloud;
DatabaseNote({
required this.id,
required this.userId,
required this.text,
required this.isSyncedWithCloud,
});
DatabaseNote.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
userId = map[userIdColumn] as int,
text = map[textColumn] as String,
isSyncedWithCloud =
(map[isSyncedWithCloudColumn] as int) == 1 ? true : false;
@override
String toString() =>
'Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text';
@override
bool operator ==(covariant DatabaseNote other) => id == other.id;
@override
int get hashCode => id.hashCode;
}
const dbName = 'notes.db';
const noteTable = 'note';
const userTable = 'user';
const idColumn = 'id';
const emailColumn = 'email';
const userIdColumn = 'user_id';
const textColumn = 'text';
const isSyncedWithCloudColumn = 'is_synced_with_cloud';
const createUserTable = '''CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL,
"email" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);''';
const createNoteTable = '''CREATE TABLE IF NOT EXISTS "note" (
"id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"text" TEXT,
"is_synced_with_cloud" INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY("user_id") REFERENCES "user"("id"),
PRIMARY KEY("id" AUTOINCREMENT)
);''';
I am creating a note taking app. A not should be added when the screen is popped. The Note is being stored in Database only once but a duplicates are being added to stream when hotrestarted the duplicate automatically goes have been trying to solve this error for quite alot of time but to no benfite. I have provided two codes add Note Screen. Which is the screen through which I add notes. NotesService contains the logic of how I add code
Add Note Screen
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mynotes/constants/colors/app_colors.dart';
import 'package:mynotes/constants/widgets/helper_text.dart';
import 'package:mynotes/services/auth/auth_services.dart';
import 'dart:developer' as devtools show log;
import 'package:mynotes/services/crud/notes_services.dart';
import 'package:mynotes/utils/show_loading_dialog.dart';
class AddNoteView extends StatefulWidget {
const AddNoteView({super.key});
@override
State<AddNoteView> createState() => _AddNoteViewState();
}
class _AddNoteViewState extends State<AddNoteView> {
DatabaseNote? _note;
late final NotesService _notesService;
late FocusNode _titleFocus;
late FocusNode _textFocus;
late TextEditingController _titleController;
late TextEditingController _textController;
late final Future<DatabaseNote> _noteFuture;
void _deleteNoteIfTextIsEmpty() {
final note = _note;
if (note != null &&
_textController.text.isEmpty &&
_titleController.text.isEmpty) {
_notesService.deleteNote(noteId: note.id);
devtools.log('deleted Note because Empty');
}
}
void _saveNoteIfTextIfTextIsNotEmpty() async {
final note = _note;
if (note != null && _textController.text.isNotEmpty ||
_titleController.text.isNotEmpty) {
final text = '${_titleController.text}&${_textController.text}';
await _notesService.updateNote(note: note!, text: text);
}
}
Future<DatabaseNote> createNewNote() async {
final existingNote = _note;
if (existingNote != null) {
return existingNote;
}
final currentUser = AuthServices.firebase().currentUser!;
final email = currentUser.email!;
final owner = await _notesService.getUser(email: email);
devtools.log("Created note: ${owner.id} Add Note View file");
return await _notesService.createNote(
owner: owner,
text: '',
);
}
@override
void initState() {
super.initState();
_notesService = NotesService.singleton();
_titleFocus = FocusNode();
_textFocus = FocusNode();
_titleController = TextEditingController();
_textController = TextEditingController();
_noteFuture = createNewNote();
Future.delayed(Duration(milliseconds: 300), () {
if (context.mounted) FocusScope.of(context).requestFocus(_titleFocus);
});
}
@override
void dispose() {
_deleteNoteIfTextIsEmpty();
_saveNoteIfTextIfTextIsNotEmpty();
_titleFocus.dispose();
_textFocus.dispose();
_titleController.dispose();
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.softWhite,
appBar: AppBar(
backgroundColor: AppColors.softWhite,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
automaticallyImplyLeading: false,
title: Transform.translate(
offset: Offset(-16, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Stack(
children: [
Icon(
Icons.arrow_back_ios_new_rounded,
size: 26,
color: AppColors.jetBlack,
),
Positioned(
left: 1,
child: Icon(
Icons.arrow_back_ios_new_rounded,
size: 26,
color: AppColors.jetBlack,
),
),
],
),
),
HelperText(
text: 'Notes',
textColor: AppColors.jetBlack,
fontSize: 18,
fontWeight: FontWeight.w600,
),
],
),
),
actions: [
IconButton(
icon:
Icon(Icons.favorite_border_rounded, color: AppColors.jetBlack),
onPressed: () {},
),
IconButton(
icon: Icon(Icons.save_alt_rounded, color: AppColors.jetBlack),
onPressed: () {},
),
],
),
body: FutureBuilder(
future: _noteFuture,
builder: (conext, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
_note = snapshot.data as DatabaseNote;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: _titleController,
focusNode: _titleFocus,
textInputAction: TextInputAction.next,
keyboardType: TextInputType.multiline,
onSubmitted: (_) =>
FocusScope.of(context).requestFocus(_textFocus),
style: GoogleFonts.lobster(
fontSize: 34,
// Larger size for title
fontWeight: FontWeight.bold,
color: AppColors.jetBlack,
),
decoration: InputDecoration(
hintText: "Title",
hintStyle: TextStyle(
fontSize: 34,
fontWeight: FontWeight.normal,
color:
Colors.grey.shade400, // Light grey hint color
),
border: InputBorder.none,
),
minLines: 1,
maxLines: 2, // Limit to two lines like Apple's Notes
),
// Spacing between title and body
Expanded(
child: TextField(
controller: _textController,
focusNode: _textFocus,
textInputAction: TextInputAction.newline,
style: TextStyle(
fontSize: 18,
color: AppColors.jetBlack,
height: 1.5, // Increase line height for readability
),
decoration: InputDecoration(
hintText: "Write your note here...",
hintStyle: TextStyle(
fontSize: 18,
color:
Colors.grey.shade400, // Light grey hint color
),
border: InputBorder.none,
),
minLines: 1,
maxLines: null,
keyboardType: TextInputType.multiline,
),
),
],
),
);
default:
return showLoadingDialog();
}
}),
);
}
}
NotesService
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:mynotes/services/crud/crud_exceptions.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' show join;
import 'dart:developer' as devtools show log;
class NotesService {
Database? _db;
List<DatabaseNote> _notes = [];
late final StreamController<List<DatabaseNote>> _notesStreamController;
//Here we are declaring a private constructor only
NotesService._sharedInstance() {
_notesStreamController = StreamController<List<DatabaseNote>>.broadcast(
onListen: () => _notesStreamController.sink.add(_notes));
}
static final NotesService _shared = NotesService._sharedInstance();
factory NotesService.singleton() => _shared;
//broadCastStream Mean It can be listened more than once;
Stream<List<DatabaseNote>> get allNotes => _notesStreamController.stream;
Future<DatabaseUser> getOrCreateUser({required String email}) async {
await ensureDbIsOpen();
try {
final user = await getUser(email: email);
return user;
} on CouldNotFindUser {
final createdUser = await createUser(email: email);
return createdUser;
} catch (e) {
rethrow;
}
}
Future<void> _cacheNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
devtools.log(_notes.toString());
_notesStreamController.add(_notes);
}
Future<DatabaseNote> updateNote({
required DatabaseNote note,
required String text,
}) async {
final db = _getDatabaseOrThrow();
//Id there is no note an exception will be thrown
await getNote(id: note.id);
final updateCount = await db.update(noteTable, {
textColumn: text,
isSyncedWithCloudColumn: 0,
});
if (updateCount == 0) {
throw CouldNotOpenNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere(
(element) => element.id == updatedNote.id,
);
devtools.log('Note is removed before updaing not shoulf == ""');
_notes.add(await getNote(id: note.id));
devtools.log(
'Before updating stream: ${_notes.length} updateNote NotesServices file');
_notesStreamController.add(_notes);
devtools.log(
'After updating stream: ${_notes.length}Update Note Note Services file');
return updatedNote;
}
}
Future<List<DatabaseNote>> getAllNotes() async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(noteTable);
devtools.log(notes.toString());
return notes.map((note) => DatabaseNote.fromRow(note)).toList();
}
Future<DatabaseNote> getNote({
required int id,
}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(
noteTable,
where: 'id = ?',
whereArgs: [id],
);
if (notes.isEmpty) {
throw CouldNotFindNote();
} else {
final note = DatabaseNote.fromRow(notes.first);
_notes.removeWhere((note) => note.id == id);
_notes.add(note);
_notesStreamController.add(_notes);
return DatabaseNote.fromRow(notes.first);
}
}
Future<int> deleteAllUsers() async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
return await db.delete(userTable);
}
Future<int> deleteAllNotes({required int userId}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
return await db.delete(noteTable);
}
Future<void> deleteNote({required int noteId}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
noteTable,
where: 'id = ?',
whereArgs: [noteId],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
}
_notes.removeWhere(
(element) => element.id == noteId,
);
_notesStreamController.add(_notes);
}
Future<DatabaseNote> createNote({
required DatabaseUser owner,
required String text,
}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final DatabaseUser dbUser = await getUser(email: owner.email);
if (dbUser != owner) {
throw CouldNotFindUser();
}
//make sure owner has the same id which is stored in the database;
final noteId = await db.insert(noteTable, {
userIdColumn: owner.id,
textColumn: text,
isSyncedWithCloudColumn: 1,
});
final note = DatabaseNote(
id: noteId,
userId: owner.id,
text: text,
isSyncedWithCloud: true,
);
devtools.log("Created note: ${owner.id} Notes Services file");
_notes.add(note);
devtools.log(
'Before updating stream: ${_notes.length} createNoteFunction notesServices file');
_notesStreamController.add(_notes);
devtools.log(
'After updating stream: ${_notes.length} create Note function Notes Services file');
return note;
}
Future<DatabaseUser> getUser({required String email}) async {
final db = _getDatabaseOrThrow();
final user = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (user.isEmpty) {
throw CouldNotFindUser();
} else {
return DatabaseUser.fromRow(user.first);
}
}
Future<DatabaseUser> createUser({required String email}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final rusults = await db.query(
userTable,
limit: 1,
where: 'email = ?',
whereArgs: [email.toLowerCase()],
);
if (rusults.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
return DatabaseUser(id: userId, email: email);
}
Future<void> deleteUser({required String email}) async {
await ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final int deletedCount =
await db.delete(userTable, where: 'email = ?', whereArgs: [
email.toLowerCase(),
]);
if (deletedCount < 1) {
throw CouldNotDeleteUser();
}
}
Database _getDatabaseOrThrow() {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}
Future<void> close() async {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}
//Insuring Database is not being opened again and again and also to open database automatically when using these functions. other eg delte user, create user,
Future<void> ensureDbIsOpen() async {
try {
await open();
} on DatabaseAlreadyOpenException {
//
}
}
//It will open and create database
Future<void> open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException();
}
try {
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath);
_db = db;
// create the user table
await db.execute(createUserTable);
// create note table
await db.execute(createNoteTable);
await _cacheNotes();
} on MissingPlatformDirectoryException {
throw UnableToGetDocumentsDirectory();
}
}
}
@immutable
class DatabaseUser {
final int id;
final String email;
const DatabaseUser({
required this.id,
required this.email,
});
DatabaseUser.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
email = map[emailColumn] as String;
@override
String toString() => 'Person, ID = $id, email = $email';
@override
bool operator ==(covariant DatabaseUser other) => id == other.id;
@override
int get hashCode => id.hashCode;
}
class DatabaseNote {
final int id;
final int userId;
final String text;
final bool isSyncedWithCloud;
DatabaseNote({
required this.id,
required this.userId,
required this.text,
required this.isSyncedWithCloud,
});
DatabaseNote.fromRow(Map<String, Object?> map)
: id = map[idColumn] as int,
userId = map[userIdColumn] as int,
text = map[textColumn] as String,
isSyncedWithCloud =
(map[isSyncedWithCloudColumn] as int) == 1 ? true : false;
@override
String toString() =>
'Note, ID = $id, userId = $userId, isSyncedWithCloud = $isSyncedWithCloud, text = $text';
@override
bool operator ==(covariant DatabaseNote other) => id == other.id;
@override
int get hashCode => id.hashCode;
}
const dbName = 'notes.db';
const noteTable = 'note';
const userTable = 'user';
const idColumn = 'id';
const emailColumn = 'email';
const userIdColumn = 'user_id';
const textColumn = 'text';
const isSyncedWithCloudColumn = 'is_synced_with_cloud';
const createUserTable = '''CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL,
"email" TEXT NOT NULL UNIQUE,
PRIMARY KEY("id" AUTOINCREMENT)
);''';
const createNoteTable = '''CREATE TABLE IF NOT EXISTS "note" (
"id" INTEGER NOT NULL,
"user_id" INTEGER NOT NULL,
"text" TEXT,
"is_synced_with_cloud" INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY("user_id") REFERENCES "user"("id"),
PRIMARY KEY("id" AUTOINCREMENT)
);''';
Share
Improve this question
edited 5 hours ago
hab
426 bronze badges
asked yesterday
Mujtaba FarhanMujtaba Farhan
13 bronze badges
1 Answer
Reset to default 0Hot restarts can lead to multiple active listeners or streams if they're not properly managed, resulting in duplicated data.
When you perform a hot restart, the Flutter framework destroys and recreates the widget tree but doesn't automatically dispose of existing streams or listeners. This behavior can cause multiple streams or listeners to remain active, leading to duplicated data in your application.
u can read more about this issue #33678