Signing out of flutter app with sqflite database - flutter

I am trying to write the code for signing out of a flutter app logging in (The database of the map is made with sqflite).
However I am getting the following error message:
flutter: NoSuchMethodError: The method 'notify' was called on null.
Receiver: null
Tried calling: notify(Instance of 'AuthState')
Have provided code of required files below.
I am trying to incorporate a sign out function in the home_screen.dart file but I feel I am missing a link between the auth.dart, auth_provider.dart, login_screen.dart and home_screen.dart files. The codes of the required files for the issue are as follows:
File: database_helper.dart
import 'dart:async';
import 'dart:io' as io;
import 'package:path/path.dart';
import 'package:better_login/user.dart';
import 'package:sqflite/sqflite.dart' ;
import 'package:path_provider/path_provider.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = new
DatabaseHelper.internal();
factory DatabaseHelper() => _instance;
static Database _db;
Future<Database> get db async {
if(_db != null)
return _db;
_db = await initDb();
return _db;
}
DatabaseHelper.internal();
initDb() async {
io.Directory documentsDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "main.db");
var theDb = await openDatabase(path, version: 1, onCreate:
_onCreate);
return theDb;
}
void _onCreate(Database db, int version) async {
// When creating the db, create the table
await db.execute(
"CREATE TABLE User(username TEXT,password TEXT)");
print("Created tables");
}
Future<int> saveUser(User user) async {
var dbClient = await db;
int res = await dbClient.insert("User", user.toMap());
return res;
}
Future<int> deleteUsers() async {
var dbClient = await db;
int res = await dbClient.delete("User");
return res;
}
Future<bool> isLoggedIn() async {
var dbClient = await db;
var res = await dbClient.query("User");
return res.length > 0? true: false;
}
}
File:auth.dart
import 'package:better_login/database_helper.dart';
enum AuthState{ LOGGED_IN, LOGGED_OUT }
abstract class AuthStateListener {
void onAuthStateChanged(AuthState state);
}
class AuthStateProvider {
static final AuthStateProvider _instance = new
AuthStateProvider.internal();
List<AuthStateListener> _subscribers;
factory AuthStateProvider() => _instance;
AuthStateProvider.internal() {
_subscribers = new List<AuthStateListener>();
initState();
}
void initState() async {
var db = new DatabaseHelper();
var isLoggedIn = await db.isLoggedIn();
if(isLoggedIn)
notify(AuthState.LOGGED_IN);
else
notify(AuthState.LOGGED_OUT);
}
void subscribe(AuthStateListener listener) {
_subscribers.add(listener);
}
void dispose(AuthStateListener listener) {
for(var l in _subscribers) {
if(l == listener)
_subscribers.remove(l);
}
}
void notify(AuthState state) {
_subscribers.forEach((AuthStateListener s) =>
s.onAuthStateChanged(state));
}
}
File: auth_provider.dart
import 'package:flutter/material.dart';
import 'package:better_login/auth.dart';
class AuthProvider extends InheritedWidget {
const AuthProvider({Key key, Widget child, this.auth}) : super(key: key, child: child);
final AuthStateListener auth;
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static AuthProvider of(BuildContext context) {
return context.inheritFromWidgetOfExactType(AuthProvider);
}
}
File: home_screen.dart
import 'package:flutter/material.dart';
import 'package:better_login/auth.dart';
import 'package:better_login/login_screen.dart';
import 'package:better_login/login_screen_presenter.dart';
import 'package:better_login/auth_provider.dart';
class HomeScreen extends StatelessWidget {
HomeScreen({this.authStateListener, this.authStateProvider});
final AuthStateListener authStateListener;
final AuthStateProvider authStateProvider;
void _signOut() async {
try{
authStateProvider.notify(AuthState.LOGGED_OUT);
authStateListener.onAuthStateChanged(AuthState.LOGGED_OUT);
}catch(e){
print(e);
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(
title: new Text("Home"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.exit_to_app), onPressed: (){_signOut();}),
],
),
body: new Center(
child: new Text("Welcome home!"),
),
);
}
}

Related

How to write and read data anywhere by shared_preferences on Flutter 3.7 background isolates?

On Flutter 3.7 platform channels can run on any isolate. So I tried this sample,
import ‘package:flutter/services.dart’;
import ‘package:shared_preferences/shared_preferences.dart’;
void main() {
// Identify the root isolate to pass to the background isolate.
// (API introduced in Flutter 3.7)
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
Isolate.spawn(_isolateMain, rootIsolateToken);
}
void _isolateMain(RootIsolateToken rootIsolateToken) async {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger
.ensureInitialized(rootIsolateToken);
// You can now use the shared_preferences plugin.
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print(sharedPreferences.getBool(‘isDebug’));
}
I can read from data on shared_preferences in this sample okey. But how can I use this feature anywhere in my app? How can I set or read data using this isolate on initState for example?
Basically you need to implement communication between isolates. You can read more about it here
Here is an example, you can change flutter_secure_storage that i used with shared_preferences package
import 'dart:async';
import 'dart:isolate';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class CreationEvent {
final RootIsolateToken isolateToken;
final SendPort sendPort;
CreationEvent(this.isolateToken, this.sendPort);
}
class DeletetionEvent {}
class ReadEvent {
final String key;
const ReadEvent(this.key);
}
class ReadResult {
final String key;
final String? content;
const ReadResult(this.key, this.content);
}
class IsolateIO {
IsolateIO._();
final _toBgPort = Completer();
final Map<Object, Completer> _completerMap = {};
Isolate? _isolate;
StreamSubscription? _fromBgListener;
void start() async {
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
ReceivePort fromBG = ReceivePort();
_fromBgListener = fromBG.listen((message) {
// setup process
if (message is SendPort) {
_toBgPort.complete(message);
return;
}
if (message is ReadResult) {
_completerMap['read:${message.key}']?.complete(message.content);
_completerMap.remove('read:${message.key}');
}
});
_isolate = await Isolate.spawn(
(CreationEvent data) {
final worker = IsolateWorker(data.isolateToken, data.sendPort);
worker.listen();
},
CreationEvent(rootIsolateToken, fromBG.sendPort),
);
}
Future<String?> readFromStorage(String key) async {
// make sure isolate created with ports
final port = await _toBgPort.future;
// store completer
final completer = Completer<String?>();
_completerMap['read:$key'] = completer;
// send key to be read
port.send(ReadEvent(key));
// return result
return completer.future;
}
void stop() async {
if (_toBgPort.isCompleted) {
final port = await _toBgPort.future;
port.send(DeletetionEvent());
}
_fromBgListener?.cancel();
_isolate?.kill(priority: Isolate.immediate);
}
static final i = IsolateIO._();
}
class IsolateWorker {
final RootIsolateToken rootIsolateToken;
final SendPort toMain;
final FlutterSecureStorage storage;
StreamSubscription? subs;
IsolateWorker(
this.rootIsolateToken,
this.toMain, {
this.storage = const FlutterSecureStorage(
aOptions: AndroidOptions(
encryptedSharedPreferences: true,
),
),
}) {
// Register the background isolate with the root isolate.
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
}
void listen() {
ReceivePort fromMain = ReceivePort();
toMain.send(fromMain.sendPort);
subs = fromMain.listen((message) => onMessage(message));
}
void onMessage(dynamic message) async {
if (message is DeletetionEvent) {
subs?.cancel();
return;
}
if (message is ReadEvent) {
final rawJson = await storage.read(key: message.key);
toMain.send(ReadResult(message.key, rawJson));
}
}
}
class View extends StatefulWidget {
const View({super.key});
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
String username = '';
#override
void initState() {
super.initState();
IsolateIO.i.start();
WidgetsBinding.instance.addPostFrameCallback((_) async {
final name = await IsolateIO.i.readFromStorage('username');
setState(() {
username = name ?? '';
});
});
}
#override
void dispose() {
IsolateIO.i.stop();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Text(username),
);
}
}

How to initialize SQLite connection

I new in Flutter.
I have already exist db and need add it to app.
Put file in assets/db/sms.db
in file pubspec.yaml
assets:
- assets/db/sms.db
Copy db file in applicationDirectory.
In file categoriesList.dart I try connect to it.
SmsDataBaseHelper.instance.getAllCategories()
I get error message.
database_helper.dart
import 'package:flutter/services.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import 'dart:io' as io;
import '/models/categories.dart';
class SmsDataBaseHelper {
static final SmsDataBaseHelper instance = SmsDataBaseHelper._();
static Database? _db;
SmsDataBaseHelper._();
Future<Database> get db async {
_db ??= await _init();
return _db!;
}
Future<Database> _init() async {
io.Directory applicationDirectory = await getApplicationDocumentsDirectory();
String dbPathSms = path.join(applicationDirectory.path, "sms.db");
bool dbExistsSms = await io.File(dbPathSms).exists();
if (!dbExistsSms) {
// Copy from asset
ByteData data = await rootBundle.load(path.join("assets", "db", "sms.db"));
List<int> bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
// Write and flush the bytes written
await io.File(dbPathSms).writeAsBytes(bytes, flush: true);
}
return await openDatabase(dbPathSms);
}
/// get all the words from categories
Future<List<Categories>> getAllCategories() async {
if (_db == null) {
throw "_db is not initiated, initiate using [init(db)] function";
}
List<Map>? categories;
await _db!.transaction((txn) async {
categories = await txn.query(
"categories",
columns: [
"name",
"ru",
],
);
});
return categories!.map((e) => Categories.fromJson(e as Map<String, dynamic>)).toList();
}
}
categoriesList.dart
import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';
import '../utils/database_helper.dart';
import '../models/categories.dart';
class CategoriesList extends StatefulWidget {
const CategoriesList({super.key});
#override
State<CategoriesList> createState() => _CategoriesListState();
}
class _CategoriesListState extends State<CategoriesList> {
//late List<Categories> categoriesList;
#override
void initState(){
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SMS Home Page'),
),
body: FutureBuilder<List<Categories>>(
future: SmsDataBaseHelper.instance.getAllCategories(),
builder: (BuildContext context, AsyncSnapshot<List<Categories>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text('start widget');
case ConnectionState.active:
case ConnectionState.waiting:
return const Text('Awaiting result from api...');
case ConnectionState.done:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
return Text('Result: ${snapshot.data}');
}
},
),
);
}
}
use this class to build database ..
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class SqlDb {
static Database? _db;
Future<Database?> get db async {
if (_db == null) {
_db = await initialDb();
return _db;
} else {
return _db;
}
}
initialDb() async {
String databasepath = await getDatabasesPath();
String path = join(databasepath, 'shop.db');
Database mydb = await openDatabase(path,
onCreate: _onCreate, version: 1, onUpgrade: _onUpgrade);
return mydb;
}
_onUpgrade(Database db, int oldversion, int newversion) async {}
deletemydatabase() async {
String databasepath = await getDatabasesPath();
String path = join(databasepath, 'shop.db');
var x = await deleteDatabase(path);
return x;
}
_onCreate(Database db, int version) async {
await db.execute(
"CREATE TABLE cart (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , title TEXT, size TEXT , code TEXT , img TEXT , price TEXT )");
}
//Select
readData(String sql) async {
Database? mydb = await db;
List<Map> response = await mydb!.rawQuery(sql);
return response;
}
//insert data
insertData(String sql) async {
Database? mydb = await db;
int response = await mydb!.rawInsert(sql);
return response;
}
deleteData(String sql) async {
Database? mydb = await db;
int response = await mydb!.rawDelete(sql);
return response;
}
updateData(String sql) async {
Database? mydb = await db;
int response = await mydb!.rawUpdate(sql);
return response;
}
}

How to Store API model object in Local Storage in flutter?

I fatch this issue during use Local Storage(shared_preferences: ^2.0.6) in my code....but i cant store the api model object in local storage...How can i do?
storeModelInPrefs() async {
http.Response response =
await http.get(Uri.parse('http://18.191.193.64/api/view_categories'));
String encodeData = jsonEncode(response.body);
///Write Data in local Storage
GetStorageUtility.prefs!.write('key', encodeData);
///Read Data from local Storage
String data = GetStorageUtility.prefs!.read('key');
if (data == null) {
print('no data in GetStorage');
} else {
Map<String, dynamic> map = jsonDecode(data);
print(map);
}
}
This is the sample example that i have created from the code that you have provided.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_app/utilities.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
GetStorageUtility.init();
super.initState();
getRemoteData();
}
getRemoteData() async {
/// This is where the api is fetching the data
var response =
await http.get(Uri.parse('http://18.191.193.64/api/view_categories'));
/// This is where the string getting
String encodeData = jsonEncode(response.body);
GetStorageUtility.write("key", encodeData);
/// this is where you fetch the data
String data = GetStorageUtility.read("key");
if (data == null) {
print('no data in GetStorage');
} else {
Map<String, dynamic> jsonData = json.decode(data);
jsonData.forEach((key, value) {
print("$key : $value\n");
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(" Page"),
),
);
}
}
SharadPrefs Singleton,
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
class GetStorageUtility {
static Future<SharedPreferences> get _instance async =>
_prefsInstance ??= await SharedPreferences.getInstance();
static SharedPreferences _prefsInstance;
static Future<SharedPreferences> init() async {
_prefsInstance = await _instance;
return _prefsInstance;
}
static String read(String key, [String defValue]) {
return _prefsInstance.getString(key) ?? defValue ?? "";
}
static Future<bool> write(String key, String value) async {
var prefs = await _instance;
return prefs?.setString(key, value) ?? Future.value(false);
}
}
Now there is on thing that you have see that you have added in you android manifest file
<application android:usesCleartextTraffic="true" />
This one should be there and the internet permission should be there in the debug and the main folders manifestfile.
This will work but this is not the best practice to store the data as string in the sharedprefs. Shared Prefs has only the job to manage the small data like bool or string. For your use case you can use a sqlite as a local data base. where you can fetch the data based on the condititions.
Let me know if it works.
Happy Coding.

An eexception occurs when using flutter_downloader package

I'm trying to use flutter_downloader package to download some files (images/pdf). There is a listView with ListTiles each containing a button to start downloading when clicked but this error occurs when scrolling the list view.
[ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: 'package:flutter_downloader/src/downloader.dart': Failed assertion: line 30 pos 12: '!_initialized': FlutterDownloader.initialize() must be called only once!
//my code is like this:
import 'dart:io';
import 'dart:isolate';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class DownloadFile extends StatefulWidget {
DownloadFile({this.downloadUrl});
final String downloadUrl;
#override
_DownloadFileState createState() => _DownloadFileState();
}
class _DownloadFileState extends State<DownloadFile> {
String downloadId;
String _localPath;
ReceivePort _port = ReceivePort();
#override
void initState(){
super.initState();
_init();
}
Future<void> _init() async {
await FlutterDownloader.initialize();
IsolateNameServer.registerPortWithName(
_port.sendPort, 'downloader_send_port');
_port.listen((dynamic data) {
String id = data[0];
DownloadTaskStatus status = data[1];
int progress = data[2];
print("status: $status");
print("progress: $progress");
print("id == downloadId: ${id == downloadId}");
});
FlutterDownloader.registerCallback(downloadCallback);
_localPath = (await _findLocalPath()) + '/Download';
final savedDir = Directory(_localPath);
bool hasExisted = await savedDir.exists();
if (!hasExisted) {
savedDir.create();
}
}
static void downloadCallback(String id, DownloadTaskStatus status, int progress) {
print(
'Background Isolate Callback: task ($id) is in status ($status) and process ($progress)');
final SendPort send =
IsolateNameServer.lookupPortByName('downloader_send_port');
send.send([id, status, progress]);
}
Future<String> _findLocalPath() async {
final directory = await getExternalStorageDirectory();
return directory.path;
}
Future<bool> _checkPermission() async {
if (Theme.of(context).platform == TargetPlatform.android) {
PermissionStatus permission = await PermissionHandler()
.checkPermissionStatus(PermissionGroup.storage);
if (permission != PermissionStatus.granted) {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler()
.requestPermissions([PermissionGroup.storage]);
if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
//----------------------------------------------------------------
#override
void dispose() {
super.dispose();
}
//---------------------------------------------------------------
#override
Widget build(BuildContext context) {
return FlatButton(
onPressed: () async {
if (await _checkPermission()) {
final taskId = await FlutterDownloader.enqueue(
url: widget.downloadUrl,
savedDir: _localPath,
showNotification:
true, // show download progress in status bar (for Android)
openFileFromNotification:
true, // click on notification to open downloaded file (for Android)
);
downloadId = taskId;
}
},
child: Text('Downloa File',style: TextStyle(color: Colors.teal),)
);
}
}
According to the Usage section in the flutter_downloader package and the error you are getting, you must call the FlutterDownloader.initialize not more than once.
You can do that in the main method of your application, just like so:
WidgetsFlutterBinding.ensureInitialized();
await FlutterDownloader.initialize();

Execute if database exists flutter

So basically what i want to do is:
When the user turns on the app for the first time,
The SQLite database is created and the data is fetched from the internet.
Until this is done, SetupPage() widget is displayed in scaffolds body or else Home() is displayed.
Now the code i wrote works perfectly for the first time, but when i open it the second time,
The SetupPage() only shows, it never goes back to the Home(). What am i doing wrong here ?
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart';
import 'package:path/path.dart';
import 'pages/home.dart';
import 'pages/SetUpPage.dart';
import 'package:sqflite/sqflite.dart';
class App extends StatefulWidget {
createState() {
return AppState();
}
}
class AppState extends State<App> {
final bgColor = const Color(0xFF1abc9c);
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool status = false;
Database database;
#override
void initState() {
initializeData();
super.initState();
}
void initializeData() async
{
var databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'demo.db');
status = false;
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE news (id INTEGER PRIMARY KEY, topic TEXT, img TEXT, newstitle TEXT, news TEXT, newslink TEXT)');
fetchData();
}
);
database.close();
}
#override
Widget build(context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("UnFound News"),
backgroundColor: bgColor,
),
body: status ? Home() : SetUpPage(),
);
}
/*Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);*/
void fetchData() async {
var result = await get("https://api.myjson.com/bins/a0bvu");
var arr = json.decode(result.body)['post'];
for(int i = 0; i < arr.length; i++)
{
//TODO: Implement addition to database.
}
setState(() {
status = true;
});
}
}
You probably want to put the fetchData in a onOpen parameter instead of the onCreate parameter like this:
Database database = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'CREATE TABLE news (id INTEGER PRIMARY KEY, topic TEXT, img TEXT, newstitle TEXT, news TEXT, newslink TEXT)');
await fetchData();
}, onOpen: (Database db) async {
setState(() {
status = true;
});
});
static Database _db;
Future<Database> get db async {
if(_db != null)
return _db;
_db = await initDb();
return _db;
}
//Creating a database with name test.dn in your directory
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "test.db");
var theDb = await openDatabase(path, version: 1, onCreate: _onCreate);
return theDb;
}
checkDb(){
//do operation
var dbClient = await db;
}