Introduction
Flutter is an excellent open-source framework for building cross-platform applications in a single codebase, ensuring good performance. This series documents my journey of building a good dev tool app for Flutter apps. Of course, it will be open source ✨
Problem
One issue that many developers face is the lack of extension support for the Flutter dev tools. There is an open issue and discussion regarding this on https://github.com/flutter/devtools/issues/1632, which was created in February 2020. Hopefully, it will be merged soon.
I plan to develop a standalone dev tool app for Flutter that will allow for awesome features, such as viewing the shared preferences state of the app or checking the state of the app, among many others.
Solution
I have been considering the idea of creating a standalone Flutter development tool for building common, useful tooling that would greatly assist developers during the development process. Initially, I would like to focus on basic tooling capabilities, such as the ability to visualise the shared_preferences
state, inspect classes, and more.
Long term vision
This dev tool is designed to take your developer experience to the next level. With the ability to integrate multiple plugins, you can easily customise the options on the dev tool per project level. This means you can have a unique development experience tailored to your specific needs.
But that's not all. With the ability to add custom commands, you can further enhance your workflow. For example, imagine having a button to sign in to the app. All you need to do is provide the code for it, and with a click of a button, you'll be signed in. It's that simple.
Current progress
I haven't really thought about a name for it yet, but for now I'm calling it "Flutter Ninja." However, I may change the name in the future. 🤞
🕵️♀️ Mode
I've been exploring how the Flutter dev tools work. It turns out they use the vm_service
package to connect to a running Flutter application and communicate with it.
You may have seen this many times while running a Flutter application. Have you ever wondered what it is? 🤔
This is the Dart VM Service Protocol. It is used to communicate with a running Dart Virtual Machine.
The methods and other important information are documented in detail here. I have been exploring it and received some advice from Norbert 🙌.
This is the current progress, I have connected to an flutter application that I have
P.S: Ignore the blurred part as it contains confidential information that cannot be shown. Additionally, there was no need to use a random app and create dummy values just to prove that this is a functional tool 🫡
The how
To begin, connect to the vm_service
client using the vmConnectUri
method.
After establishing the connection, retrieve the main isolate of the running Flutter application.
Future<Isolate?> getIsolate() async {
final isolates = vm.isolates ?? [];
if (isolates.isEmpty) {
return null;
}
for (var isolate in isolates) {
log('Isolate: ${isolate.name}');
}
log('Isolates found: ${isolates.length}');
isolate = await vmService.getIsolate(isolates.first.id!);
return isolate;
}
To recognise the main isolate, we can rely on isolate.name
. The name of the main isolate is 🥁 Main
. However, typically, the first isolate is the Main
one, which we can use to identify it.
To obtain a list of all available classes, use the getClassList
method with the isolateId
parameter, which we obtained earlier.
Once you have this list, you can use the getInstances
method to retrieve the instances.
The Instance
is one of the most useful class, it has many important details with it, you can find them all here, but predominately we would need valueAsString
, elements
and associations
TL;DR
-
valueAsString
- when the return type isbool
,int
,string
,double
-
elements
- when the return type isList
orSet
-
associations
- when the return type isMap
Evaluate
This is the method that does the magic 🪄, it takes in expression
executes it and returns the result.
Example
If the expression is 1+1
the evaluated result will be 2
as string.
Class A {
bool doMagic() {
return true;
}
}
For example we have an instance of a class A
we evaluate
the expression doMagic
the result would be true
in string format.
This is the snippet from the repository that helped me achieve it!
Future<Instance?> evaluate({required String targetId, required String expression}) async {
final Response result = await vmService.evaluate(isolate.id!, targetId, expression);
return await getObjectFromResult(result.json?['id']);
}
Future<Instance?> getObjectFromResult(String resultId) async {
final result = await vmService.getObject(isolate.id!, resultId);
final Instance? instance = result as Instance?;
return instance;
}
Next Steps?
Stay Engaged for What's Next!
This journey has just begun, with Part 1 being the first glimpse into our vision. In the upcoming post, we'll delve deeper, exploring how we seamlessly integrated SharedPreferences
state into our powerful dev tool, along with a host of other intriguing details.
Moreover, the repository for this project will be thoroughly cleaned and made open-source, paving the way for you to join me in shaping its future. Be sure to follow me on GitHub at https://github.com/Rohithgilla12 so you won't miss the moment the repository goes live. Together, we're set to embark on an exciting development journey.
Stay tuned! 🚀🙌
Top comments (1)
Very good article. More of these!