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

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

Related

Flutter - Check the content of a Json before saving it locally and properly

Please i need some help and thanks in advance.
I am reciving over Websocket a Json that i want to safe locally.
But before saving it, i am trying to compare the recieved Json with the existend locad Json. If the value of count_json does not exist inside the local file it would need to save the Json to a new line and if the value of count_json would already exist it would do nothing.
At this moment, i am able to save it localy, and write it down to a new line in the file.
But i have two problems that i do not know how to solve it.
How i am making the comparising is not good. Because it is saving the recieved Json to a new line even the value of count_jsonalready exist, like as follow.
{"count_json":1,"range_json":[5.5,8.9,7.5,6.7,8.7],"force_json":[5.4,5.3,5.2,5.2,5.1]}
{"count_json":1,"range_json":[9.5,8.3,12.4,13.1,8.5],"force_json":[4.9,4.8,4.8,4.9,5]}
{"count_json":1,"range_json":[11.7,9.7,9.9,11.8,10.2],"force_json":[4.9,5,5.2,5.3,5.5]}
{"count_json":2,"range_json":[19.6,19.6,19.6,19.6,19.6],"force_json":[10,10,10,10,10]}
{"count_json":2,"range_json":[19.4,19.6,19.6,19.6,19.6],"force_json":[9.9,10,10,10,10]}
{"count_json":2,"range_json":[19.4,19.6,19.6,19.6,19.6],"force_json":[9.9,10,10,10,10]}
{"count_json":2,"range_json":[19.4,19.7,19.6,19.6,19.6],"force_json":[9.9,10,10,10,10]}
.
.
But i am expenting this
{"count_json":1,"range_json":[5.5,8.9,7.5,6.7,8.7],"force_json":[5.4,5.3,5.2,5.2,5.1]}
{"count_json":2,"range_json":[19.4,19.7,19.6,19.6,19.6],"force_json":[9.9,10,10,10,10]}
.
.
My approach on how to compara and safe is as follow.
Map<String, dynamic> jsondat = json.decode(message);
String data = json.encode(jsondat);
setState(() {
if (data.contains("count_json")) {
istcycles = jsondat['count_json']; //cycles value
connectedS1Status = true;
if (_myjson['count_json'] != 0) {
_filePath.writeAsString('$data\n', mode: FileMode.append);
}
}
});
If i open the local Json file in Visual Studio code it give me the error message
End of file Expected
That means the stuctur of how i a writing and saving to the Json file is not properly. See above how the structure is inside the file.
Follow the complete code.
//https://docs.flutter.dev/cookbook/persistence/reading-writing-files
// ignore_for_file: avoid_print
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:web_socket_channel/io.dart';
import 'dart:io';
import 'dart:async';
const String fileName = 'myJsonFile.json';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: HomePage());
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
// ignore: library_private_types_in_public_api
_HomePageState createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
late IOWebSocketChannel channel;
late bool
connectedS1Status; //boolean value to track if WebSocket is connected
late int istcycles; //variable for istcycles
late double istforcesensor;
late double istrangesensor;
#override
void initState() {
connectedS1Status =
false; //initially connection status is "NO" so its FALSE
Future.delayed(Duration.zero, () async {
channelconnect(); //connect to WebSocket wth NodeMCU
});
// Instantiate _controllerKey and _controllerValue
print('0. Initialized _myjson: $_myjson');
_readJson();
istcycles = 0; //initial value of istcycles
istforcesensor = 0;
istrangesensor = 0;
super.initState();
}
channelconnect() {
try {
channel = IOWebSocketChannel.connect(
"ws://192.168.1.100:80"); //channel IP : Port
channel.stream.listen(
(message) {
//print(message);
Map<String, dynamic> jsondat = json.decode(message);
String data = json.encode(jsondat);
setState(() {
if (data.contains("count_json")) {
istcycles = jsondat['count_json']; //cycles value
connectedS1Status = true;
if (_myjson['count_json'] != 0) {
_filePath.writeAsString('$data\n', mode: FileMode.append);
}
}
});
},
onDone: () {
print("Web socket is closed");
setState(() {
connectedS1Status = false;
});
},
onError: (error) {
print(error.toString());
},
);
} catch (_) {
print("error on connecting to websocket.");
}
}
bool _fileExists = false;
late File _filePath;
// First initialization of _json (if there is no json in the file)
late Map<String, dynamic> _myjson = {};
late String _myjsonString;
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/$fileName');
}
//------------------------------------------------------------------------------------
// _readJson--------------------------------------------------------------------------
//------------------------------------------------------------------------------------
void _readJson() async {
// Initialize _filePath
_filePath = await _localFile;
// 0. Check whether the _file exists
_fileExists = await _filePath.exists();
print('0. File exists? $_fileExists');
// If the _file exists->read it: update initialized _json by what's in the _file
if (_fileExists) {
try {
//1. Read _jsonString<String> from the _file.
_myjsonString = await _filePath.readAsString();
print('1.(_readJson) _jsonString: $_myjsonString');
//2. Update initialized _json by converting _jsonString<String>->_json<Map>
_myjson = jsonDecode(_myjsonString);
print('2.(_readJson) _json: $_myjson \n - \n');
} catch (e) {
// Print exception errors
print('Tried reading _file error: $e');
// If encountering an error, return null
}
}
}
#override
void dispose() {
super.dispose();
}
// Delete Function-------------------------------------------
Future<int> deleteFile() async {
try {
final file = await _localFile;
await file.delete();
} catch (e) {
return 0;
}
return 0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("WebSocket Json"),
backgroundColor: Colors.redAccent),
body: Container(
alignment: Alignment.topCenter, //inner widget alignment to center
padding: const EdgeInsets.all(20),
child: Column(
children: [
Container(
//showing if websocket is connected or disconnected
child: connectedS1Status
? const Text("WEBSOCKET: CONNECTED")
: const Text("DISCONNECTED")),
Text("Cycles: $istcycles "),
],
)),
);
}
}

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.

Flutter Pusher Websocket package not working

I have a backend Laravel application that uses Pusher for notifications. I would like to show notifications in my Flutter app (both iOS and Android). I found that https://pub.dev/packages/pusher_websocket_flutter/ package has the best score, but I can't get it to work. I've followed this tutorial, and I get no errors (whatever I put for my APP_KEY, which must be wrong), but I never get anything shown.
Has anyone managed to get this working, or should I switch to firebase?
This is my pusher_service.dart:
import 'package:flutter/services.dart';
import 'package:pusher_websocket_flutter/pusher.dart';
import 'dart:async';
class PusherService {
Event lastEvent;
String lastConnectionState;
Channel channel;
StreamController<String> _eventData = StreamController<String>();
Sink get _inEventData => _eventData.sink;
Stream get eventStream => _eventData.stream;
Future<void> initPusher() async {
try {
await Pusher.init('XXX', PusherOptions(cluster: 'XX'), enableLogging: true);
print("Pusher initialized");
}
on PlatformException catch (e) {
print(e.message);
}
}
void connectPusher() {
Pusher.connect(
onConnectionStateChange: (ConnectionStateChange connectionState) async {
lastConnectionState = connectionState.currentState;
print("Pusher connected");
}, onError: (ConnectionError e) {
print("Error: ${e.message}");
});
}
Future<void> subscribePusher(String channelName) async {
channel = await Pusher.subscribe(channelName);
print("Pusher subscribed to channel");
}
void unSubscribePusher(String channelName) {
Pusher.unsubscribe(channelName);
}
void bindEvent(String eventName) {
channel.bind(eventName, (last) {
final String data = last.data;
_inEventData.add(data);
});
print("Pusher data binded");
}
void unbindEvent(String eventName) {
channel.unbind(eventName);
_eventData.close();
}
Future<void> firePusher(String channelName, String eventName) async {
await initPusher();
connectPusher();
await subscribePusher(channelName);
bindEvent(eventName);
}
}
My pusher_test.dart:
import 'package:flutter/material.dart';
import 'package:chalet/services/pusher_service.dart';
import 'package:pusher/pusher.dart';
import 'dart:async';
class PusherTest extends StatefulWidget {
#override
_PusherTestState createState() => _PusherTestState();
}
class _PusherTestState extends State<PusherTest> {
PusherService pusherService = PusherService();
#override
void initState() {
pusherService = PusherService();
pusherService.firePusher('public', 'create');
testPusher();
super.initState();
}
#override
void dispose() {
pusherService.unbindEvent('create');
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: StreamBuilder(
stream: pusherService.eventStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return Container(
child: Text(snapshot.data),
);
},
),
),
);
}
}
I've checked and my snapshot.connectionState is always waiting.
Try this:
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:pusher_client/pusher_client.dart';
//instantiate Pusher Class
class PusherController {
static final PusherController _pusherController =
PusherController._internal();
factory PusherController() {
return _pusherController;
}
PusherController._internal();
PusherClient pusher;
Channel channel;
StreamController<String> _eventData = StreamController<String>.broadcast();
Sink get _inEventData => _eventData.sink;
Stream get eventStream => _eventData.stream;
String channelName = "";
String prevChannelName = "";
String eventName = "";
void initPusher() {
PusherOptions options = PusherOptions(
cluster: "eu",
);
pusher = new PusherClient("key", options,
autoConnect: true, enableLogging: true);
}
void setChannelName(String name) {
channelName = name;
print("channelName: ${channelName}");
}
void setEventName(String name) {
eventName = name;
print("eventName: ${eventName}");
}
void subscribePusher() {
channel = pusher.subscribe(channelName);
pusher.onConnectionStateChange((state) {
log("previousState: ${state.previousState}, currentState: ${state.currentState}");
});
pusher.onConnectionError((error) {
log("error: ${error.message}");
});
//Bind to listen for events called and sent to channel
channel.bind(eventName, (PusherEvent event) {
print("xxxxxxxxx From pusher xxxxxxxxx");
print('xxxxx This is Event name - $eventName xxxx');
print('xxxxx This is Event gotten - ${event.data} xxx');
_inEventData.add(event.data);
prevChannelName = eventName;
});
}
void connectPusher() {
pusher.connect();
}
void disconnectPusher() async {
await channel.unbind(eventName);
await pusher.disconnect();
}
}
Then use streamBuilder and stream from evenStream.

How to save event with sharedpreference in flutter

Hello I try to use this timeline package.
https://github.com/softmarshmallow/flutter-timeline
It's work fine to create timeline after press button but I don't success to save events with sharedpreference. I would like to restore history of the timeline at the initState.
TimelineEventDisplay get plainEventDisplay {
return TimelineEventDisplay(
child: TimelineEventCard(
title: Text("just now"),
content: Text("someone commented on your timeline ${DateTime.now()}"),
),
indicator: TimelineDots.of(context).circleIcon);
}
List<TimelineEventDisplay> events;
Widget _buildTimeline() {
return TimelineTheme(
data: TimelineThemeData(lineColor: Colors.blueAccent),
child: Timeline(
indicatorSize: 56,
events: events,
));
}
void _addEvent() {
setState(() {
events.add(plainEventDisplay);
});
}
#override
void initState() {
events = [
plainEventDisplay,
];
}
Create a SharedPref class so that it would be easy for you to manage things.
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
class SharedPref {
read(String key) async {
final prefs = await SharedPreferences.getInstance();
if(prefs.getString(key) == null){
return null;
}
final map = jsonDecode(prefs.getString(key));
return map;
}
save(String key, value) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(key, jsonEncode(value));
}
remove(String key) async {
final prefs = await SharedPreferences.getInstance();
prefs.remove(key);
}
}
In your Flutter widget, create initState as follows:
SharedPref _prefs = SharedPref();
final events;
#override
void initState() async {
super.initState();
events = await _prefs.read('events');
}
void _addEvent() async {
setState(() {
events.add(plainEventDisplay);
});
await _prefs.save('events', events);
}

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();