There are currently all these open issues for flutter_secure_storage, all mention the exception MissingPluginException(No implementation found for method read on channel plugins.it_nomads.com/flutter_secure_storage)
in the issue or in the comments:
#5 - Sporadic finalizer exceptions for underlying KeyStore on Android
#49 - MissingPluginException (MissingPluginException(No implementation found for method read on channel plugins.it_nomads.com/flutter_secure_storage))
#71 - MissingPluginException
#83 - MissingPluginException(No implementation found for method write on channel plugins.it_nomads.com/flutter_secure_storage)
#85 - doesn't work on api level 23
#118 - Android 4.4.2: MissingPluginException(No implementation found for method write on cannel plugins.it_nomads.com/flutter_secure_storage))
#129 - Not Working in android 5.1.1 and 4.4.4
#136 - Can't run flutter test due to MissingPluginException in flutter_secure_storage
I'm also experiencing this exception with my end users, but with 8 open issues it's difficult to have to a sensible conversation - can I suggest we use this one thread to resolve this?
Most suggested "solutions" are to uninstall / reinstall the app, flutter clean
, etc. however this simply isn't a solution - you can't ask end users to reinstall your app.
One suggestion I have is that based on the data below, it looks like this is an Android only problem - so perhaps there is a problem with how the plugin is registered on Android? The fact that it only happens sometimes, for some users, then occasionally it magically disappears, also suggests it is a timing issue.
Affected versions
After a quick survey of the issues, the exception has been reported on the following versions:
- Flutter versions: 1.17.0, 1.17.5, 1.7.8, 1.12.13,
- Android versions: 4.4.4, 5.1.1, 6, 8, 10
Affected devices
- Emulator and real devices
- Devices: Samsung J3, Samsung Galaxy Grand 2, Galaxy Tabs, Moto e5, pixel 3a, Galaxy J7 2015, Samsung note 3
Some questions for flutter_secure_storage developers
To try to help, when looking through the code the following questions came to mind (I'm not a plugin developer so you may have to bear with me!)
Silently swallowing exceptions
There are a couple of places where you catch Exceptions, but then just Log and do nothing else:
try {
applicationContext = context.getApplicationContext();
...
channel.setMethodCallHandler(this);
} catch (Exception e) {
Log.e("FlutterSecureStoragePl", "Registration failed", e);
}
try {
Log.d("FlutterSecureStoragePl", "Initializing StorageCipher");
storageCipher = new StorageCipher18Implementation(applicationContext);
Log.d("FlutterSecureStoragePl", "StorageCipher initialization complete");
} catch (Exception e) {
Log.e("FlutterSecureStoragePl", "StorageCipher initialization failed", e);
}
Could these be surfaced to flutter via an error Result instead of silently discarded? This might reveal where there are actual problems when encountered.
Use of volatile
You're using volatile
here:
private volatile StorageCipher storageCipher;
and link to a very long and complicated article on double-checked locking. Can you explain the reason behind using this instead of simply using an AsyncTask
to perform work on a separate thread? If I had to bet money on why we get MissingPluginException
, this would be it - edge case race conditions caused by complex multi-threading code.
Are you passing the right context?
In initInstance
you are doing this:
applicationContext = context.getApplicationContext();
preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
charset = Charset.forName("UTF-8");
StorageCipher18Implementation.moveSecretFromPreferencesIfNeeded(preferences, context);
Notice you are getting and assigning the application context first, but then passing the context from the parameter to the moveSecretFromPreferencesIfNeeded
method - is that right? I have no idea if that's a problem, just thought I should ask the question.
BinaryMessenger
It looks like you're getting a BinaryMessenger
directly from the binding object here to give to the MethodChannel
:
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
initInstance(binding.getBinaryMessenger(), binding.getApplicationContext());
}
However the latest plugin template gets the BinaryMessenger
via the flutter engine like this:
channel = MethodChannel(flutterPluginBinding.binaryMessenger.getFlutterEngine().getDartExecutor(), "flutter_plugin")
Is that a problem do you think?
Setting method channel to null
onDetachedFromEngine
you're setting channel to null. Is this necessary? Might it not cause a problem if channel
is accessed after this is called? The latest plugin template doesn't do this, it just sets channel.setMethodCallHandler(null)
and nothing else.
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
channel = null;
}
bug question