Git Product home page Git Product logo

mynotes-course's Introduction

mynotes-course's People

Contributors

vandadnp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mynotes-course's Issues

CouldNotFindUser

notes_view.dart=>

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter_application_1/constants/routes.dart';
import 'package:flutter_application_1/services/crud/crud_exceptions.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart'
show getApplicationDocumentsDirectory, MissingPlatformDirectoryException;
import 'package:path/path.dart' show join;

class NotesService {
Database? _db;
// to create singleton class only one instace of this is created in whole project
static final NotesService _shared = NotesService._sharedInstance();
NotesService._sharedInstance() {
_notesStreamController = StreamController<List>.broadcast(
onListen: () {
_notesStreamController.sink.add(_notes);
},
);
}
factory NotesService() => _shared;

// _notes is cached data where CRUD operation done
List _notes = [];
// manipulation of data in pipe is through _notesStreamController
// if you listen to changes in pipe and do hot reload then error occurs thats why broadcast here which closes the current listening channel
// before listening them again you must close the previos one
late final StreamController<List> _notesStreamController;
Stream<List> get allNotes => _notesStreamController.stream;

Future getOrCreateUser({
required String email,
}) async {
try {
print('inside getorcreate user');
final user = await getUser(email: email);
print('User=>$user');
return user;
} on CouldNotFindUser {
final createdUser = await createUser(email: email);
print('Created User=>$createdUser');
return createdUser;
} catch (e) {
rethrow;
}
}

// _cachedNotes() show warning because it is private and not been used and other are public
Future _cachedNotes() async {
final allNotes = await getAllNotes();
_notes = allNotes.toList();
_notesStreamController.add(_notes);
}

Future updateNote({
required DatabaseNote note,
required String text,
}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
// make sure note exists
await getNote(id: note.id);
// update db
final updatesCount = await db.update(noteTable, {
textColumn: text,
isSyncedWithCloudColumn: 0,
});
if (updatesCount == 0) {
throw CouldNotUpdateNote();
} else {
final updatedNote = await getNote(id: note.id);
_notes.removeWhere((note) => note.id == updatedNote.id);
_notes.add(updatedNote);
_notesStreamController.add(_notes);
return updatedNote;
}
}

Future<Iterable> getAllNotes() async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(noteTable);
return notes.map((noteRow) => DatabaseNote.fromRow(noteRow));
}

Future getNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final notes = await db.query(
noteTable,
limit: 1,
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 note;
}
}

Future deleteAllNote() async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final numberOfDeletions = await db.delete(noteTable);
_notes = [];
_notesStreamController.add(_notes);
return numberOfDeletions;
}

Future deleteNote({required int id}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
noteTable,
where: 'id=?',
whereArgs: [id],
);
if (deletedCount == 0) {
throw CouldNotDeleteNote();
} else {
// safeguard added please check
final countBefore = _notes.length;
_notes.removeWhere((note) => note.id == id);
if (_notes.length != countBefore) {
_notesStreamController.add(_notes);
}
}
}

Future createNote({required DatabaseUser owner}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();

// make sure owner exists in the database with correct id
await getUser(email: owner.email);

const text = '';

// Check if the user_id is correct
print('Creating note for user_id: ${owner.id}');

// create the note
final noteId = await db.insert(noteTable, {
  userIdColumn: owner.id,
  textColumn: text,
  isSyncedWithCloudColumn: 0, // Assuming default value for new note
});

final note = DatabaseNote(
  id: noteId,
  userId: owner.id,
  text: text,
  isSyncedWithCloud: true,
);

_notes.add(note);
_notesStreamController.add(_notes);

return note;

}

Future getUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
print('Database=>$db');
print('Fetching user for email: $email');
final results = await db.query(
userTable,
limit: 1,
where: 'email=?',
whereArgs: [email.toLowerCase()],
);
print('Results for user query: $results');
if (results.isEmpty) {
print('Could not find user with email: $email');
throw CouldNotFindUser();
} else {
final row = DatabaseUser.fromRow(results.first);
print('Found user: $row');
return row;
}
}

Future createUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final results = await db.query(
userTable,
limit: 1,
where: 'email=?',
whereArgs: [email.toLowerCase()],
);
print('Results in createUser =>$results');
if (results.isNotEmpty) {
throw UserAlreadyExists();
}
final userId = await db.insert(userTable, {
emailColumn: email.toLowerCase(),
});
print('userId=>$userId');
return DatabaseUser(
id: userId,
email: email,
);
}

Future deleteUser({required String email}) async {
await _ensureDbIsOpen();
final db = _getDatabaseOrThrow();
final deletedCount = await db.delete(
userTable,
where: 'email=?',
whereArgs: [email.toLowerCase()],
);
if (deletedCount != 1) {
throw CouldNotDeleteUser();
}
}

Database _getDatabaseOrThrow() {
final db = _db;
// print('Database\n');
print(db?.database);
// print('Database:$db');
if (db == null) {
throw DatabaseIsNotOpen();
} else {
return db;
}
}

Future close() async {
final db = _db;
if (db == null) {
throw DatabaseIsNotOpen();
} else {
await db.close();
_db = null;
}
}

Future _ensureDbIsOpen() async {
if (_db == null) {
await open();
}
}
// Add this function to handle database migrations
// Future _migrate(Database db, int oldVersion, int newVersion) async {
// if (oldVersion < 2) {
// // Assuming version 2 introduces the is_synced_with_cloud column
// await db.execute('ALTER TABLE note ADD COLUMN is_synced_with_cloud INTEGER NOT NULL DEFAULT 0');
// }
// }

Future _ensureColumnExists(
Database db, String table, String column) async {
final result = await db.rawQuery('PRAGMA table_info($table)');
print('result=>$result');
final columnExists =
result.any((element) => element['name'] == column);
print('column exist=>$columnExists');
if (!columnExists) {
await db.execute(
'ALTER TABLE $table ADD COLUMN $column INTEGER NOT NULL DEFAULT 0');
}
}

Future open() async {
if (_db != null) {
throw DatabaseAlreadyOpenException();
}
try {
final docsPath = await getApplicationDocumentsDirectory();
final dbPath = join(docsPath.path, dbName);
final db = await openDatabase(dbPath, version: 1);

  _db = db;

  // Ensure tables exist
 try{ 
  await db.execute(createUserTable);
  await db.execute(createNoteTable);
  }
  catch(err){

print('SOmething wrong happend $err');
}

  // Check and add column if not exists
  await _ensureColumnExists(db, noteTable, isSyncedWithCloudColumn);

  await _cachedNotes();

} 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';

// allow you to change the behavior of input parameter so that they do not confirm the signature of parameter in super class
@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? ?? 0) == 1;
@OverRide
String toString() =>
'Note,ID=$id, userId= $userId, isSyncedWithCloudColumn=$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)
);''';

firebase_authProvider.dart

import 'package:firebase_auth/firebase_auth.dart'
show FirebaseAuth, FirebaseAuthException;
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_application_1/firebase_options.dart';
import 'package:flutter_application_1/services/auth/auth_provider.dart';

import 'package:flutter_application_1/services/auth/auth_user.dart';
import 'package:flutter_application_1/services/auth/auth_exceptions.dart';

class FirebaseAuthProvider implements AuthProvider {
// check2
@OverRide
Future initialize() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
}

@OverRide
Future createUser({
required String email,
required String password,
}) async {
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
final user = currentUser;
if (user != null) {
print('User created => $user');
return user;
}
// ignore: curly_braces_in_flow_control_structures
else {
throw UserNotFoundAuthException();
}
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
throw WeakPasswordAuthException();
} else if (e.code == 'email-already-in-use') {
throw EmailAlreadyInUseAuthException();
} else if (e.code == 'invalid-email') {
throw InvalidEmailAuthException();
} else {
throw GenericAuthException();
}
} catch (_) {
throw GenericAuthException();
}
}

@OverRide
AuthUser? get currentUser {
final user = FirebaseAuth.instance.currentUser;
print('current user =>$user');
if (user != null) {
print('convert fibase user to AuthUser=>${AuthUser.fromFirebase(user)}');
return AuthUser.fromFirebase(user);
} else {
return null;
}
}

@OverRide
Future logIn({
required String email,
required String password,
}) async {
try {
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
final user = currentUser;
print('login user=> $user');
if (user != null) {
return user;
}
// ignore: curly_braces_in_flow_control_structures
else {
throw UserNotFoundAuthException();
}
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
throw UserNotFoundAuthException();
} else if (e.code == 'wrong-password') {
throw WrongPasswordAuthException();
} else {
throw GenericAuthException();
}
} catch (_) {
throw GenericAuthException();
}
}

@OverRide
Future logOut() async {
final user = FirebaseAuth.instance.currentUser;
print('logout user=>$user');
if (user != null) {
await FirebaseAuth.instance.signOut();
} else {
throw UserNotLoggedInAuthException();
}
}

@OverRide
Future sendEmailVerification() async {
final user = FirebaseAuth.instance.currentUser;
print('Send email verification=>$user');
if (user != null) {
await user.sendEmailVerification();
} else {
throw UserNotLoggedInAuthException();
}
}
}

notes_view.dart

import 'package:flutter/material.dart';
import 'package:flutter_application_1/services/auth/auth_service.dart';
import 'package:flutter_application_1/services/crud/notes_service.dart';

class NewNoteView extends StatefulWidget {
const NewNoteView({super.key});

@OverRide
State createState() => _NewNoteViewState();
}

class _NewNoteViewState extends State {
@OverRide
initState() {
_notesService = NotesService();
_textController = TextEditingController();
super.initState();
}

DatabaseNote? _note;
late final NotesService _notesService;
late final TextEditingController _textController;

void _textControllerListener() async {
final note = _note;
print('note=>$note');
if (note == null) {
return;
}
final text = _textController.text;
print('Text=>$text');
await _notesService.updateNote(
note: note,
text: text,
);
}

void _setupTextControllerListener() {
_textController.removeListener(_textControllerListener);
_textController.addListener(_textControllerListener);
}
@OverRide
void dispose() {
_deleteNoteIfTextIsEmpty();
_saveNoteIfTextNotEmpty();
_textController.dispose();
super.dispose();
}
// step1
Future createNewNote() async {
print('inside createNewNote()');
final existingNote = _note;
print('existing note=>$existingNote');
if (existingNote != null) {
return existingNote;
}
// if currentUser is null then we want app to crash so ! it is safe to use
final currentUser = AuthService.firebase().currentUser!;
final email = currentUser.email!;
print('Email=>$email');
final owner = await _notesService.getUser(email: email);
print('Owner=>$owner');
final note = await _notesService.createNote(owner: owner);
print('note here=>$note');
return note;
}

void _deleteNoteIfTextIsEmpty() async {
final note = _note;
if (_textController.text.isEmpty && note != null) {
_notesService.deleteNote(id: note.id);
}
}

void _saveNoteIfTextNotEmpty() async {
final note = _note;
final text = _textController.text;
if (note != null && text.isNotEmpty) {
await _notesService.updateNote(
note: note,
text: text,
);
}
}

@OverRide
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('New Note'),
),
body: FutureBuilder(
future: createNewNote(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
// print('Data:$snapshot.data');
if (snapshot.hasData) {
_note = snapshot.data as DatabaseNote;
}
print('new_note_view=>$_note');
_setupTextControllerListener();
return TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: const InputDecoration(
hintText: 'Start typing your text...',
),
);
default:
return const CircularProgressIndicator();
}
},
),
);
}
}

please find out the error it show Results=>[]
and stuck in debug mode where CouldNotFindUser

Error in Ch 31: Expected a value of type 'DatabaseNote', but got one of type 'Null'

Hi there,

I'm an old-shool developer & haven't been hand-on coding for over 20 yrs and hoping to upskill by completing this tutorial.
Just working my way through your tutorial; all going great till I got to Chapter 31: Creating New Notes and I'm getting an error:
"Expected a value of type 'DatabaseNote', but got one of type 'Null'"
Don't know where to start troubleshooting, so hoping for some expert assistance.
FYI - I'm not using the Android emulator; just testing using the web app version - is that the problem here?
Based on comments in an earlier issue, it seems that code implemented in this chapter is tailored to smart phone devices, rather than a web app. Is there a version of code that works on the web?
image

UpdateNotes method in step-15 updates all notes instead of only one note

I found a problem in the following snippet of your amazing flutter course in step-15 at notes_service.dart

 Future<DatabaseNote> updateNote({
    required DatabaseNote note,
    required String text,
  }) async {
    await _ensureDbIsOpen();
    final db = _getDatabaseOrThrow();
    // make sure note exists
    await getNote(id: note.id);
    // >>>>>>>>>>>>>>>> The problem origin
    final updatesCount = await db.update(noteTable, {
      textColumn: text,
      isSyncedWithCloudColumn: 0,
    });
// >>>>>>>>>>>>>
    if (updatesCount == 0) {
      throw CouldNotUpdateNote();
    } else {
      final updatedNote = await getNote(id: note.id);
      _notes.removeWhere((note) => note.id == updatedNote.id);
      _notes.add(updatedNote);
      _notesStreamController.add(_notes);
      return updatedNote;
    }
  }

The result of this problem only prevails upon hot restart/reload of the app, as all the list elements will get the same value as the last created note due to that issue.

I fixed the problem in my codebase by specifying the exact id of the updated note, and the problem no longer persists

The fix

// update DB
    final updatesCount = await db.update(
      noteTable,
      {
        textColumn: text,
        isSyncedWithCloudColumn: 0,
      },
      where: 'id = ?',
      whereArgs: [note.id],
    );

Regards

Bug on update.

I have just found a tiny bug in updateNote function in notes_service. If you add a note and close the app, after restart you will see that the text of all your notes will be the same and equal to the value of the last note. Hot restart also reproduces this behaviour.

The actual fix is to add id of note to a update query. Please see a snippet below

final updCount = await db.update(noteTable, where: 'id =?', whereArgs: [ id ], { textCol: text, });

Classes and mixins can only implement other classes and mixins. Try specifying a class or mixin

pls remove this error.

Classes and mixins can only implement other classes and mixins.
Try specifying a class or mixin, or remove the name from the list. in class auth_test.dart

class MockAuthProvider implements AuthProvider {
AuthUser? _user;
var _isInitialized = false;
bool get isInitialized => _isInitialized;
@OverRide
Future createUser({
required String email,
required String password,
}) async {
if (!isInitialized) throw NotInitializedException();
await Future.delayed(const Duration(seconds: 1));
return logIn(
email: email,
password: password,
);
}

@OverRide
AuthUser? get currentUser => _user;

@OverRide
Future initialize() async {
await Future.delayed(const Duration(seconds: 1));
_isInitialized = true;
}

@OverRide
Future logIn({
required String email,
required String password,
}) {
if (!isInitialized) throw NotInitializedException();
if (email == '[email protected]') throw UserNotFoundAuthException();
if (password == '12345') throw WrongPasswordAuthExeption();
const user = AuthUser(isEmailVerified: false);
_user = user;
return Future.value(user);
}

@OverRide
Future logOut() async {
if (!isInitialized) throw NotInitializedException();
if (_user == null) throw UserNotFoundAuthException();
await Future.delayed(const Duration(seconds: 1));
_user = null;
}

@OverRide
Future sendEmailVerification() async {
if (!isInitialized) throw NotInitializedException();
final user = _user;
if (user == null) throw UserNotFoundAuthException();
const newUser = AuthUser(isEmailVerified: true);
_user = newUser;
}
}

Notes are not shown in view.

Hi, I am following the course and have a problem on notes view. StreamBuilder is in connection state done and doesn't display the notes from the list. In the course we are displaying with the state active.

Here is what I am doing:
body: FutureBuilder(
future: _notesService.getOrCreateUser(email: userEmail),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return StreamBuilder(
stream: _notesService.allNotes,
builder: (context, builder) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allNotes = snapshot.data as List;
return ListView.builder(
itemCount: allNotes.length,
itemBuilder: (context, index) {
final note = allNotes[index];
return ListTile(
title: Text(
note.text,
maxLines: 1,
softWrap: true,
overflow: TextOverflow.ellipsis,
),
);
},
);
} else {
return const CircularProgressIndicator();
}

                default:
                  return const CircularProgressIndicator();
              }
            },
          );

Has Firebase changed. Getting error in code from Video

Great course, many thanks. New to Flutter/dart, been using Nativescript and looking to move over to Flutter.

I get this on the unknown user.

E/RecaptchaCallWrapper( 8269): Initial task failed for action RecaptchaAction(action=signInWithPassword)with exception - An internal error has occurred. [ INVALID_LOGIN_CREDENTIALS ]
E/flutter ( 8269): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: [firebase_auth/INVALID_LOGIN_CREDENTIALS] An internal error has occurred. [ INVALID_LOGIN_CREDENTIALS ]

INVALID_LOGIN_CREDENTIALS rather than user-not-found.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.