Read shared preferences in Flutter app that were previously stored in a native app - flutter

I have the following problem: right now there is an app in the PlayStore that is written in native code (both iOS and Android) which I'm planning on migrating to flutter. My aim is that the users don't notice there were changes under the hood but can continue using the app like before. For that I need to migrate the shared preferences as well. This is, however, quite difficult. In the native Android application I stored the relevant shared preference like this:
SharedPreferences sharedPrefs = context.getSharedPreferences(
"storage",
Context.MODE_PRIVATE
);
sharedPrefs.putString('guuid', 'guuid_value');
editor.apply();
which results in a file being created at this path:
/data/data/patavinus.patavinus/shared_prefs/storage.xml
with this content:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<int name="guuid" value="guuid_value" />
</map>
If I use shared_prefences in Flutter to obtain this value by doing this:
final sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.getString('guuid');
it returns null because it looks for
/data/data/patavinus.patavinus/shared_prefs/FlutterSharedPreferences.xml which is the file that is written to when using shared_preferences in Flutter to store shared preferences. Because the shared prefs were written in native app context, the file is obviously not there.
Is there any way to tell Flutter to look for /data/data/patavinus.patavinus/shared_prefs/storage.xml without having to use platform channel?
I know how this works the other way around like it's mentioned here: How to access flutter Shared preferences on the android end (using java). This way is easy because in Android you can choose to prepend Flutter's prefix. However, in Flutter you can't.
I am also aware of this plugin: https://pub.dev/packages/native_shared_preferences however, I can't believe that a third party plugin is the recommended way. Also, I have spread the relevant shared preferences across multiple resource files. In this plugin, you can only set one (by specifying the string resource flutter_shared_pref_name).

As suggested here you can get the native file's content and copy it to a new file. You can copy the content to flutter's storage file when the user upgrades to your flutter app for the first time.

Best option is to store shared preferences in native part in same file as SharedPreferences plugin does it. So it means to do like that:
In Java code replace your SharedPreferences code, with same key, as it is in plugin: SharedPreferences sharedPref = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE);
In native part save all preferences with prefix "flutter.". So in native part you will get needed preferences like this: String test = sharedPref.getString("flutter.test", "");

Since there was no satisfying answer to my question that works for both of the major operating systems and taking into account the distribution over multiple resource files, I have written a platform channel solution myself.
On Android (Kotlin) I let the caller provide a "file" argument because it's possible to have the data spread over multiple resource files (like I described in my question).
package my.test
import android.content.*
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity: FlutterActivity() {
private val CHANNEL = "testChannel"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
call, result ->
when (call.method) {
"getStringValue" -> {
val key: String? = call.argument<String>("key");
val file: String? = call.argument<String>("file");
when {
key == null -> {
result.error("KEY_MISSING", "Argument 'key' is not provided.", null)
}
file == null -> {
result.error("FILE_MISSING", "Argument 'file' is not provided.", null)
}
else -> {
val value: String? = getStringValue(file, key)
result.success(value)
}
}
}
else -> {
result.notImplemented()
}
}
}
}
private fun getStringValue(file: String, key: String): String? {
return context.getSharedPreferences(
file,
Context.MODE_PRIVATE
).getString(key, null);
}
}
On iOS (Swift) this is not necessary as I'm working with UserDefaults
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let platformChannel = FlutterMethodChannel(
name: "testChannel",
binaryMessenger: controller.binaryMessenger
)
platformChannel.setMethodCallHandler({
[weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
guard call.method == "getStringValue" else {
result(FlutterMethodNotImplemented)
return
}
if let args: Dictionary<String, Any> = call.arguments as? Dictionary<String, Any>,
let number: String = args["key"] as? String, {
self?.getStringValue(key: key, result: result)
return
} else {
result(
FlutterError.init(
code: "KEY_MISSING",
message: "Argument 'key' is not provided.",
details: nil
)
)
}
})
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
private func getStringValue(key: String, result: FlutterResult) -> String? {
let fetchedValue: String? = UserDefaults.object(forKey: key) as? String
result(fetchedValue)
}
I'd wish for the SharedPreferences package to add the possibility of omitting the flutter prefix, enabling the developer to migrate content seamlessly from native apps.
I also wrote a blog post that explains the problem and the solution in a little bit more details: https://www.flutterclutter.dev/flutter/tutorials/read-shared-preferences-from-native-apps/2021/9753/

I am also aware of this plugin: https://pub.dev/packages/native_shared_preferences however, I can't believe that a third party plugin is the recommended way.
I think the third party plugin is just fine. The best thing about flutter is its amazing community that keeps contributing thoroughly to the ecosystem. That said, I would not recommend keeping using the non-official library. So you could test migrating your shared preferences behind the scenes.
In your initState query for flutter shared preferences.
If they exist, skip.
If they don't exist, query for native shared preferences, copy if exist
And done. This will help with the migration IMHO.

My choice is to treat it just as a file.
In flutter read the android shared preference file
/data/data/patavinus.patavinus/shared_prefs/storage.xml
take the values in the way you handle other xml file.
If you want to make some changes, don't use sharedPrefs.put, Instead write into the file (/data/data/patavinus.patavinus/shared_prefs/storage.xml
) using file's write method in flutter

Related

How to route an RSocket client in Flutter

this is java (webflux) code.
public Mono<String> sendMessage(SendMessageRequest requestBody, RSocketRequester rSocketRequester) {
Long userId = clientManager.getUserIdBySocket(rSocketRequester);
Chat chat = Chat.builder()
.chatroomId(requestBody.getChatroomId())
.createdDate(LocalDateTime.now())
.lastModifiedDate(LocalDateTime.now())
.userId(userId)
.build();
return chatRepository.save(chat)
.flatMap(entity -> chatReadRepository.findByUserIdAndChatroomId(userId, entity.getChatroomId()))
.flatMapMany(entity -> chatMemberRepository.findAllByChatroomIdAndUserIdNot(entity.getChatroomId(), userId))
.map(ChatMember::getUserId)
.map(clientManager::getSocketByUserId)
.flatMap(socketOptional ->
socketOptional.<org.reactivestreams.Publisher<String>>map(socketRequester -> socketRequester.route("chat.receive")
.data(requestBody)
.send()
.thenReturn("Success!"))
.orElseGet(() -> Mono.just("fail!"))
)
.collectList().thenReturn("Success!");
}
I use this code In java
socketRequester -> socketRequester.route("chat.receive")
.data(requestBody)
.send()
.thenReturn("Success!"))
.orElseGet(() -> Mono.just("fail!"))
This is my flutter code. I use rsocket: ^1.0.0.
void main() async {
String jwt = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiVVNFUiJdLCJpYXQiOjE2NTk1OTg1OTgsImV4cCI6MTY1OTYwODU5OH0.bJHn4IKm6DtnGAQAxyruRb-LJSgKt-72-L9g7JqBtHw";
var rSocket = await RSocketConnector.create()
.setupPayload(routeAndDataPayload("socket.acceptor", jwt))
.connect('tcp://192.168.219.101:8081');
var payload = await rSocket.requestResponse!(routeAndDataPayload("healthcheck", "data!!!"));
print(payload.getDataUtf8());
rSocket.close();
runApp(const MyApp());
}
How can i receive this message in flutter?
I wanna make controller "chat.receive" route in flutter.
if java code,
#MessageMapping("chat.receive")
but i don't know flutter's #MessageMapping...
First of all it seems that rsocket: ^1.0.0. is not working properly and you need to use
rsocket:
git:
url: git#github.com:rsocket/rsocket-dart.git
ref: master
instead.
Then you can create a Futute like below as you connection holder:
final Future<RSocket> _rsocketConnectionStream = RSocketConnector.create()
.keepAlive(2000, 999999999)
.connect('ws://192.168.1.188:8080')
.catchError((error) => print(error));
Now you can make RSokcet calls like this:
Payload routeAndDataPayload(String route, String data) {
var compositeMetadata =
CompositeMetadata.fromEntries([RoutingMetadata(route, List.empty())]);
var metadataBytes = compositeMetadata.toUint8Array();
var dataBytes = Uint8List.fromList(utf8.encode(data));
return Payload.from(metadataBytes, dataBytes);
}
Stream<String?> fetchSeekersNames(String name, double latitude, double longitude, double searchingRadius) {
var body = """
{
"location": {
"latitude": $latitude,
"longitude": $longitude
},
"radius": $searchingRadius,
"dancerPartnerSeekerName": "$name"
}
""";
return _rsocketConnectionStream
.asStream()
.asyncExpand((rSocket) => rSocket.requestStream!(
routeAndDataPayload("/api/XXXX", body)))
.map((element) => element!.getDataUtf8())
.doOnError((p0, p1) => print(p1));
}
I couldn't do it without routeAndDataPayload method. I wrote it after days of research.
There is no #MessageMapping equivalent in Dart as far as I know.
The RSocket library doesn't support Bi-Directional Channels.
But if you are trying make your flutter code keep the RSocket base connection, and do request-stream on top of it, I haven't been able to pull that out.
I couldn't make it work over TCP. I had to use webSocket for my RSocket underlying transport. But how to keep that websocket/rsocket connection ongoing in flutter code, .... !?
Please write here in case you found something.
All in all, I'm also

Multiple local HMS ML Kit translator models in Flutter?

I've defined a class that wraps the HMS ML Kit in-device translator.
This class has two translator instances, with two different settings:
MLLocalTranslator translatorSend = new MLLocalTranslator();
MLLocalTranslator translatorReceive = new MLLocalTranslator();
MLTranslateSetting settingSend = new MLTranslateSetting();
MLTranslateSetting settingReceive = new MLTranslateSetting();
translatorSend translates request from a language (for example it) to English (en). translatorReceive translates the response of the request from en to it.
However, the prepare method only downloads the model for en_it translation and not the it_en model (if exists).
HMSTranslator(String languageCode) {
settingSend.sourceLangCode = languageCode;
settingSend.targetLangCode = "en";
settingReceive.sourceLangCode = "en";
settingReceive.targetLangCode = languageCode;
}
Future<bool> prepare() async {
if(settingSend.sourceLangCode != settingSend.targetLangCode) {
bool isSendPrepared = await translatorSend.prepareModel(setting: settingSend)
bool isReceivePrepared = await translatorReceive.prepareModel(setting: settingReceive);
isPrepared = isSendPrepared && isReceivePrepared;
}
else {
isPrepared = false;
}
return isPrepared;
}
The problem comes when I translate a string with translatorSend.
Future<String> translateString(String stringToTranslate) async {
if(settingSend.sourceLangCode != settingSend.targetLangCode) {
String result;
if (isPrepared) {
result = await translatorSend.asyncTranslate(sourceText: stringToTranslate);
}
else {
settingSend.sourceTextOnRemote = stringToTranslate;
result = await sendRemoteTranslator.asyncTranslate(setting: settingSend);
}
return result;
}
else {
return stringToTranslate;
}
}
This method should translate an it String to an en String. However, it seems to call the en_it model and fails the translation:
I/flutter (28228): TRANSLATOR: it to en
I/flutter (28228): TRANSLATOR: PREPARED
I/MLLocalTranslator(28228): translate sourceLanguage: en targetLanguage: it
WHAT: vestiti usati -> vestiti usati - WHERE applicazione -> applicazione
The translation of the response, from en to it works.
I've tested other languages and that happens also with fr.
Further testing showed that the process worked with es:
WHAT: ropa usada -> Used clothing - WHERE aplicación -> application
Pls check whether you are using the new version of the Flutter plug-in.
Language packs can be used in two-way. For example, en-it can be used for en to it or it to en.
The following are for your reference:
Modify based on the plugin Demo in the official website
The same instance is used for bidirectional translation by invoking multiple times.
//Entry function
_translationMake() async {
try {
await _prepareModel_run("it","en","vestiti usati");
await _prepareModel_run("en","it","application");
} on Exception catch (e) {
print(e.toString());
}
}
_prepareModel_run(String srcLang, String dstLang, String content) async {
setting.sourceLangCode = srcLang;
setting.targetLangCode = dstLang;
try {
final bool res = await translator.prepareModel(setting: setting);
if (res) {
final String s = await _localTranslate_run(content);
if (s != null) {
print("_prepareModel_run " + content + " translate to "+s);
}
}else {
print("_prepareModel_run res false");
}
} on Exception catch (e) {
print(e.toString());
}
}
Future<String> _localTranslate_run(String Content) async {
try {
final String s =
await translator.syncTranslate(sourceText: Content);
if (s != null) {
_stopLocalTranslate();
setState(() => _translateResult = s);
return s;
} else {
print("no Translation");
setState(() => _translateResult = "no translation");
return "no translation";
}
} on Exception catch (e) {
print(e.toString());
}
}
And the log print results are as follows:
_prepareModel_run vestiti usati translate to Used clothes
_prepareModel_run application translate to applicazione
We can use HMS ML kit to translate text into different languages. The following is the info. you can take reference for.
ML services can currently translate texts between 12 languages: Simplified Chinese, English, French, Arabic, Thai, Spanish, Turkish, Portuguese, Japanese, German, Italian, and Russian.
Step 1: Text is fetched from UI and provided to ML model
Step 2: Parameters are set before making API call to server
    · Source language code
    · Desired Language code
    · String which needs to be translated.
Step 3: Once API data reaches the server ML Model translates the text into desired output
Step 4: Server returns the translated output to application.
Step 5: Application shows output to UI.
Precautions: The machine learning model is stored on cloud. An Internet call is made so its permission is required.
Below are the changes you have to do in order to run build and run the project
Open App.gradle file and add this line on top.
apply plugin: 'com.huawei.agconnect'
To use Text Translation service add this dependency to pro
mplementation 'com.huawei.hms:ml-computer-translate:1.0.3.300'
MLRemoteTranslateSetting object is being created which takes Source Language as setSourceLangCode() and Output Language as setTargetLangCode()
MLRemoteTranslator object is created by passing previously created MLRemoteTranslateSetting object to it.
You can create a Task where mlRemoteTranslator will have an async call by asyncTranslate() and we will provide user string as the input to this method.
This task will yield to 2 callbacks
addOnSuccessListener
addOnFailureListener
As the name suggests you can add your code in success listener and can add notification/Log in failure listener.
For Flutter:
First:
create MlTranslatorSettings object and instance in initState
Second:
set the settings to the translator, for example, initial language and final language of the translation, see below example.
In the properties, you can customize the type of map, controls, camera position, initial position, etc.
Here are also some detailed info. regarding how to use HMS ML kit with Flutter:
Link Hope it will be helpful to you.

IOException in mReferrerClient.getInstallReferrer() with HMS Core version 5.0.1.307

I want to test my own app before going live after integrating with Huawei install Referrer SDK. I followed all the steps found in codelabs and the documentations and when i install the apk on the device , getInstallReferrer method throws IOException. This is my code. What is it that i am doing wrong ?. how can i get installReferrer info for testing purposes ?
Runnable {
referrerClient = newBuilder(context).setTest(true).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
#SuppressLint("SwitchIntDef")
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> {
// Connection established.
try {
val response: ReferrerDetails = referrerClient.installReferrer
val referrerUrl: String = response.installReferrer
val referrerClickTime: Long = response.referrerClickTimestampSeconds
val appInstallTime: Long = response.installBeginTimestampSeconds
}catch (e : IOException){
Log.i("INSTALL_REFERRER","IOException")
}
catch(e: RemoteException){
Log.i("INSTALL_REFERRER","RemoteException")
}
finally {
referrerClient.endConnection()
}
}
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> {
Log.i("INSTALL_REFERRER","NOT AVAILABLE")
}
InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> {
Log.i("INSTALL_REFERRER","SERVICE UNAVAILABLE")
}
}
}
override fun onInstallReferrerServiceDisconnected() {
Log.i("INSTALL_REFERRER","ReferrerServiceDisconnected")
}
})
}.run()
Please check whether the AIDL interface is added.
Check the screenshot below:
Obtain Install Referrer Information by AIDL
You can call an AIDL API provided by HUAWEI Ads Kit to obtain install referrer information from HUAWEI devices, without integrating any HUAWEI SDK. The install referrer information obtained from a device in either mode (SDK or AIDL) is the same.
Call Process
The Development Procedure is as follows
Create an AIDL file for the IPPSChannelInfoService API and save the file.
Copy the following content to the AIDL file:
package com.huawei.android.hms.ppskit;
/** Important: Do not change the method sequence in the AIDL file. */
interface IPPSChannelInfoService {
String getChannelInfo();
}
Change Build Action to AndroidInterfaceDescription of AIDL file.
Rebuild project.
Create a class to implement Android-native IServiceConnection.
For more details, see docs.
Also, please kindly refer to the demo.
Update:
The package name needs to be specified because setTest(true)
if (this.isTest){
var2 = "com.huawei.pps.hms.test";
}
The empty check on ReferrerDetails can be added.
if (null != referrerDetails && null != mCallback)

Returning a binary file from a custom route (not FileMiddeleware's public folder)

I want to return a file via a route that I don't want public. My users will need to use a URL with a unique and one time code. Because of that I don't want to use the public folder via the FileMiddleware.
This is a binary executable, not a text file. I can grab the file as data with FileManager, but it seems like Future<Data> is not a valid return type for a route.
After searching StackOverflow, the only thing I've found is this reply to a similar question: How do I download a file and send a file using Vapor server side swift?
But that doesn't really fill in much on HOW to accomplish it.
router.get("customfile") { req -> Future<Response> in
return try req.streamFile(at: "/path/to/file")
}
This works for me.
func routes(_ app: Application) throws {
app.get("downloadthefile") { req -> Response in
let filePath = "path/to/the/file"
let result = req.fileio.streamFile(at: filePath)
return result
}
}

NullReferenceException occurs during offline sync to Azure Mobile Service

I am trying to make offline sync to table from azure mobile service. My Xamarin Form version is 1.4.2.6359.
I try to test my code in OnCreate method of MainActivity. All preparation steps such as MobileServiceClient initialization, MobileServiceSQLiteStore initialization, SyncTable creation, etc are ok.
When I try to call PullAsync, I am getting NullReferenceException. I capture the package using Package Capture App from mobile. The request goes to Azure Mobile service and it returns the correct json data successfully.
When I try the same code in Xamarin Android project (not Xamarin Form), it is working fine.
To reproduce the issue.
Just create Xamarin Form (Portable) project and use my code.
My Code
private async Task Test() {
const string applicationURL = #"https://xxxx.azure-mobile.net/";
const string applicationKey = #"xxxx";
CurrentPlatform.Init();
var client = new MobileServiceClient(applicationURL, applicationKey);
string path = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "store.db");
if (!File.Exists(path)) {
File.Create(path).Dispose();
}
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<Product>();
await client.SyncContext.InitializeAsync(store);
var productTable = client.GetSyncTable<Product>();
try {
await client.SyncContext.PushAsync();
await productTable.PullAsync("allProducts", productTable.CreateQuery());
var t = await productTable.ToListAsync();
Console.WriteLine("Product Count : " + t.Count);
}
catch (Java.Net.MalformedURLException ex) {
Console.WriteLine(ex.Message);
}
catch (Exception e) {
Console.WriteLine(e.Message);
}
}
References:
http://azure.microsoft.com/en-us/documentation/articles/mobile-services-xamarin-android-get-started-offline-data/
http://blogs.msdn.com/b/carlosfigueira/archive/2014/04/07/deep-dive-on-the-offline-support-in-the-azure-mobile-service-managed-client-sdk.aspx
I got the solution for this case.
As far as my understanding, this is what is happening. During the application is loading, I call PullAsync. It is async call and during this call, application keeps loading other components. The actual NullReferenceException is coming from OnPrepareOptionsMenu function (Xamarin.Forms.Platform.Android.AndroidActivity.OnPrepareOptionsMenu). The exception is happening on other thread and the thread simply dies. That's why I cannot get stack trace from my main thread.
This NullReferenceException issue is totally not related to Azure Mobile Service.
I override OnPrepareOptionsMenu in MainActivity and add try-catch block to base class function call. The problem is solved. Here is my code in MainActivity class.
public override bool OnPrepareOptionsMenu(IMenu menu) {
try {
// I am always getting menu.HasVisibleItems = false in my app
if (menu != null && menu.HasVisibleItems) {
// Exception is happening when the following code is executed
var result = base.OnPrepareOptionsMenu(menu);
return result;
}
}
catch{
}
return true;
}
I don't really understand why it is happening. Please point me out if you have better understanding of this case and better solution.
I think my issue is same as this : http://forums.xamarin.com/discussion/23579/exception-whilte-trying-to-open-activity-from-xamarin-forms-page