I'm having a simple Flutter plugin that communicates through a MethodChannel
with some native Android code. Here's a simple example how the methods provided by the plugin look like:
Future<void> doSomethingImportant(int address, SomeImportantData importantData) async {
String jsonButStillImportant = jsonEncode(importantData.toJson());
return methodChannel.invokeMethod('doSomethingImportant', {"address": address, "data": jsonButStillImportant});
}
When the doSomethingImportant()
(java) method is done, it delivers the result to Flutter by calling:
public void onSuccess(Result result, Object value) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
result.success(value);
Log.d("Debug", "Why does this occasionally cause a timeout at Flutter?!?");
}
});
}
Then, during tests, the method is called like this:
void _someTestMethod() async {
await doSomethingImportant(anAddress, theMostImportantDataInTheWorld).timeout(const Duration(seconds: 1)).catchError((e) {
print("Something's wrong here: "+e.toString());
return;
});
}
This is working well most of the time. Sometimes, however, the test fails because doSomethingImportant()
runs into a timeout, and the following shows up at logcat:
2021-12-15 19:56:50.919 D/Debug: Why does this occasionally cause a timeout at Flutter?!?"
2021-12-15 19:56:51.697 I/flutter: Something's wrong here: TimeoutException after 0:00:01.000000: Future not completed
Now I'm wondering what can possibly cause the timeout and how to prevent it. What's noticeable is that the timeout message always appears about 700-800 milliseconds after result.success()
was called.
Edit 1:
The problem is that sometimes the future does not complete, even though result.success()
is called. This happens only sporadically, but if it does, the timeouts purpose is to gracefully exit the calling function. Simply removing the timeout is unfortunately not an option, because it would block forever.
Edit 2 - some more background information:
Please note that the above code snippets of doSomethingImportant()
and _someTestMethod()
are just examples to illustrate how the methods look like. (Except onSuccess()
, this one's an exact copy from the production code).
The actual plugin controls a Bluetooth Mesh network. _someTestMethod()
sends and receives data to verify that the mesh network and the firmware running on the individual nodes are doing what they are supposed to do. The logic in _someTestMethod()
looks like this:
- Send data into the mesh.
- Read data from the mesh.
- Verify that the read data matches the expected result.
The await/timeout()
approach has been chosen because each step depends on the successful completion of the previous step. Nesting those calls in .then()
callbacks would be an alternative, but I simply prefer the top-to-bottom await/timeout()
-spaghetti-code to nested .then()
callbacks.
The problem is, that every now and then, MethodChannel.Result.success()
is called at Android, but Flutter does not complete the Future
. If that happens, the Future stays uncompleted for all of eternity and logcat does not tell anything neither.
Again, this happens only sporadically. Most of the time it is working just fine.
Now the question is, what's wrong here? Is there any other way to convince the Future
to complete? Any ideas are welcome Thank you!