This is a crash course to learn a few of the advanced topics in flutter such as state management, changing themes, storing data in local storage, etc.
Final Demo video of the app:
- Basic Knowledge about Flutter and Dart.
- Flutter SDK
- Android Studio or VSCode
To setup Flutter in Android Studio check here
To setup Flutter in VSCode check here
- Make sure you have the Flutter SDK installed and other prerequisites.
- Fork the repository and clone it to your local machine.
- Open Android Studio or VSCode and import the project.
- To download the dependencies run
flutter pub get
at the project's root directory. - To run the app run
flutter run
at the project's root directory.
- The features are divided into 3 parts
- But first, let's start with the Starter code for the app.
- In the starter code there is a
MyHomePage
class which is theHome page
, used to show the notes. - Also I have created a
custom textfiled
to use in other places. - And at last, there is a
Note
model which would contain thetitle
anddescription
of the note.
Below is the list of features that can be used to learn the topics.
- Add
date
to theNote
model. - Create a new screen to show the
Note.title
andNote.desc
. - Update and delete operations for notes.
- Create a
Snackbar
on the Update and Delete operation. - Add
Slidable
to denote the Update and Delete feature. - If you get stuck anywhere you can find the code here, and the guide can be found here.
- Add provider package for state management.
- Separate theme data to be changed on theme toggle.
- Add shared preferences package to store the theme preference.
- Dynamic theme changing. (Light/Dark Theme)
- If you get stuck anywhere you can find the code here, and the guide can be found here.
- Add sqllite database to store notes.
- Add CRUD operations in the database for notes.
- If you get stuck anywhere you can find the code here, and the guide can be found here.
-
Code for basic level features can be found here.
-
Install Packages :
- To install any package run
flutter pub add <package name>
in the root of the project. - flutter_slidable
- intl
- To install any package run
-
Add the
date
property in theNote
model to store theDateTime
of note creation.-
After this, the constructor of the
Note
model will look like this: note.dartclass Note { final String title; final String desc; final DateTime date; Note({ required this.title, required this.desc, required this.date, }); }
-
-
Add
details.dart
screen to display note details.-
Navigate to
details.dart
file when theListTile
is pressed. -
Code for details page is given below: details.dart
import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import '../models/note.dart'; class Details extends StatelessWidget { final Note note; const Details({ Key? key, required this.note, }) : super(key: key); @override Widget build(BuildContext context) { DateFormat _dateFormat = DateFormat('hh:mm, d MMMM yy'); return Scaffold( backgroundColor: Theme.of(context).backgroundColor, appBar: AppBar( backgroundColor: Theme.of(context).primaryColor, title: Text('Note Details!'), ), body: Center( child: Container( width: double.infinity, color: Theme.of(context).accentColor, margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(16), child: Column( children: [ Text( note.title, textScaleFactor: 2, style: TextStyle( color: Theme.of(context).primaryColor, fontWeight: FontWeight.bold, ), ), SizedBox(height: 20), Text( 'Date: ${_dateFormat.format(note.date)}', textScaleFactor: 1.3, style: TextStyle(color: Theme.of(context).primaryColor), ), SizedBox(height: 30), Text( note.desc, textScaleFactor: 1.2, ), ]), ), ), ); } }
-
-
Add a
snackbar
to show on note's CRUD operations.- Docs for
SnackBar
class can be found here - Code for snackbar is given below:
void snackbar(String message) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(message))); }
- Docs for
-
Add
Slidable
as a parent widget to theListTile
and add update and delete operations to theactions
andsecondaryActions
of the widget.- Docs for
Slidable
package can be found here - With
update
anddelete
features to the_notes
list ie._notes[index] = <Updated Note>
and_notes.removeAt(index);
respectively. - (Note: I have modified the
addNote
function to_addUpdateNote
with optional parameters to update the note.) - After this, the
ListTile
withSlidable
as a parent widget will look like this: home.dartSlidable( actionPane: SlidableDrawerActionPane(), actions: [ IconSlideAction( caption: 'Update', color: Colors.blue, icon: Icons.update, onTap: () => _addUpdateDialog( title: _notes[index].title, desc: _notes[index].desc, type: 'Update', index: index, id: _notes[index].id, ), ), ], secondaryActions: [ IconSlideAction( caption: 'Delete', color: Colors.red, icon: Icons.delete, onTap: () async { setState(() { _notes.removeAt(index); }); snackbar("Deleted Note!"); }, ), ], child: ListTile( leading: Icon( Icons.note, color: Theme.of(context).primaryColor, ), title: Text( _notes[index].title, textScaleFactor: 1.2, style: TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text(_notes[index].desc), trailing: Icon( CupertinoIcons.forward, color: Theme.of(context).primaryColor, size: 35, ), onTap: () => Navigator.push( context, MaterialPageRoute( builder: (context) => Details( note: _notes[index], ), ), ), ), );
- Docs for
-
Finished with the Basic level Features of the app🎉.
-
Code for intermediate-level features can be found here
-
Install Packages :
- To install any package run
flutter pub add <package name>
in the root of the project. - provider
- shared_preferences
- To install any package run
-
Create a
theme.dart
inside theutil
folder for storing theme-related constants.-
Add the
isDark
property to change the theme data of the app based on the value. -
The
theme.dart
file will look like this: theme.dartimport 'package:flutter/material.dart'; class Styles { static ThemeData themeData({ required bool isDark, }) { return ThemeData( primarySwatch: Colors.red, primaryColor: Colors.red, accentColor: isDark ? const Color(0xFF161B22) : Colors.white, backgroundColor: isDark ? const Color(0xFF010409) : const Color(0xFFEEEEEE), brightness: isDark ? Brightness.dark : Brightness.light, visualDensity: VisualDensity.adaptivePlatformDensity, ); } }
-
-
Add
dark_notifier.dart
for retrieving from local storage and notify the listeners of theme change-
Docs for
SharedPreferences
package can be found here -
Docs for
Provider
package can be found here -
More details about local storage and theming be found video.
-
The
dark_notifier.dart
file will look like this: dark_notifier.dartimport 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; class DarkNotifier with ChangeNotifier { PrefsState _currentPrefs = const PrefsState(); DarkNotifier() { _loadDarkPref(); } Future<void> _loadDarkPref() async { await SharedPreferences.getInstance().then((prefs) { final bool darkPref = prefs.getBool('isDark') ?? false; _currentPrefs = PrefsState(darkMode: darkPref); }); notifyListeners(); } Future<void> _saveDarkPref() async { await SharedPreferences.getInstance().then((prefs) { prefs.setBool('isDark', _currentPrefs.darkMode); }); } bool get isDark => _currentPrefs.darkMode; set darkMode(bool newValue) { if (newValue == _currentPrefs.darkMode) return; _currentPrefs = PrefsState(darkMode: newValue); notifyListeners(); _saveDarkPref(); } } class PrefsState { final bool darkMode; const PrefsState({this.darkMode = false}); }
-
-
Changes at
main.dart
has to be done for state management and theming.-
The
main.dart
file will look like this: main.dartvoid main() { runApp(MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => DarkNotifier(), ), ], child: MyApp(), )); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Workshop', theme: Styles.themeData( isDark: Provider.of<DarkNotifier>(context).isDark, ), debugShowCheckedModeBanner: false, home: MyHomePage(), ); } }
-
-
Add the
Switch
button on the home screen for switching the theme.- Code for the
Switch
button is given below: home.dartSwitch( value: _isDark, onChanged: (value) { _isDark = value; Provider.of<DarkNotifier>(context, listen: false).darkMode = _isDark; }, ),
- Code for the
-
Congratulations! Finished with the Intermediate level Features of the app🎉.
-
Code for advanced level features can be found here
-
Install Packages :
-
Add
SQlite
database to store notes. -
Create SQL Table in the
main
function in the database for storing notes. -
Docs of
SQlite
package can be found here -
Flutter example for
SQlite
can be found here. -
Code to create table in
main
function is given below: main.dartvoid main() async { WidgetsFlutterBinding.ensureInitialized(); await openDatabase( join(await getDatabasesPath(), 'notes_database.db'), onCreate: (db, version) { return db.execute( 'CREATE TABLE note(id INTEGER PRIMARY KEY, title TEXT, desc Text, date Text)', ); }, version: 1, ); runApp(MultiProvider( providers: [ ChangeNotifierProvider( create: (_) => DarkNotifier(), ), ], child: MyApp(), )); }
-
Add CRUD operations for notes SQlite database in
models/note.dart
.-
Add the
id
property inside theNote
class as the primary key for the database. -
Code for CRUD operations in
note.dart
is given below: note.dartimport 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; class Note { final int id; final String title; final String desc; final DateTime date; static late final Future<Database> _database; Note({ required this.id, required this.title, required this.desc, required this.date, }); static Future<void> _loadDatabase() async { _database = openDatabase( join(await getDatabasesPath(), 'notes_database.db'), version: 1, ); } Map<String, dynamic> toMap() { return { 'id': id, 'title': title, 'desc': desc, 'date': date.toString(), }; } @override String toString() { return 'Note{id: $id, title: $title, desc: $desc, data: ${date.toString()}}'; } static Future<void> insertNote(Note note) async { final db = await _database; await db.insert( 'note', note.toMap(), conflictAlgorithm: ConflictAlgorithm.replace, ); print('Note Inserted'); } static Future<List<Note>> getNotes() async { // Get a reference to the database. await _loadDatabase(); final db = await _database; // Query the table for all The Notes. final List<Map<String, dynamic>> maps = await db.query('note'); print('Note Got'); // Convert the List<Map<String, dynamic> into a List<Note>. return List.generate(maps.length, (i) { return Note( id: maps[i]['id'], title: maps[i]['title'], desc: maps[i]['desc'], date: DateTime.parse(maps[i]['date']), ); }); } static Future<void> updateNote(Note note) async { // Get a reference to the database. final db = await _database; // Update the given Note. await db.update( 'note', note.toMap(), // Ensure that the Note has a matching id. where: 'id = ?', // Pass the Note's id as a whereArg to prevent SQL injection. whereArgs: [note.id], ); print('Note Updated: ${note.id}'); } static Future<void> deleteNote(int id) async { // Get a reference to the database. final db = await _database; // Remove the Note from the database. await db.delete( 'note', // Use a `where` clause to delete a specific Note. where: 'id = ?', // Pass the Note's id as a whereArg to prevent SQL injection. whereArgs: [id], ); print('Note Deleted: $id'); } }
-
Check the addition of CRUD operations of
note
database in home.dart
-
-
Congratulations! Finished with the Advanced level Features of the app🎉.