Commencing a journey of migrating Pocket Paint to Flutter
The past couple of months have been an interesting experience. My project for Google Summer of Code was originally to refactor tests from Java to Kotlin. It changed during the community bonding period and I was excited about it.
Discussions were in motion to move to a cross-platform framework. Mainly because the iOS side of things wasn't looking so good in terms of the number of features as compared to Android. And of course, the advantage of half the team size with one codebase is good too. What next? Choosing the right hybrid framework :D
At that time, there were two viable choices: KMM (Kotlin Multiplatform Mobile) and Flutter.
In KMM, the common code (business logic and other common code) is written in Kotlin and all the UI code is written separately for both platforms in their respective native languages. Read more
This seemed like the obvious choice; the code was already there for Android and that too in Kotlin, only had to write the UI code for iOS. Well, not as simple as it sounds. Flutter was a better choice than KMM in my opinion -
- KMM just reached alpha. Flutter for mobile has been in production for a few years now.
- It has a large arsenal of useful packages and one of the best bunch of resources out there.
- JIT & AOT compilation (read more is just great developer experience!
I have had work experience with Flutter before but never tried KMM. Decided to give it a try before jumping to conclusions. Made a simple drawing app (repo) that draws a hard-coded stroke on both platforms. A few thoughts -
- Took wayyy too long for the Gradle sync and initial build to finish for a hello world app.
- Kotlin has a nice community on Slack which proved to be quite helpful. I was having problems with generating a common API for the
Color
class. Searched for the relevant keywords and found a decent solution in themultiplatform
channel. Good support overall. - Quite a few things were in the experimental phase, it sure is to change in the coming months though.
- Since both platforms have declarative solutions for UI (Jetpack Compose for Android and SwiftUI for iOS), it was an almost as good experience as Flutter's "hot reload/restart".
The app did work in the end but going with KMM at its current stage would not be the right choice for Pocket Paint. I believe now we've established that Flutter was the way to go.
Since this was going to be a repository from scratch, as a true Flutter developer, I had to burn my brains out by searching for the "best" state management solution out there, haha. It's a pain to find the right one, but I chose Riverpod as my poison. It has concise documentation and also acts as a service locator on top of being an awesome state management solution.
Before explaining the project structure, I would like to define a few things mentioned in the diagram -
-
A
red arrow
represents the direction of dependency.Example: X --> Y means that X depends on Y
-
State
is an immutable class of single/multiple properties that control aUI component
.Example: In our app, we have a state called
WorkspaceState
that has a propertyisFullscreen
. This property controls two parts of the UI - the exit fullscreen icon button and the app bars (bottom and top). They are hidden or shown based on theisFullscreen
property. -
UI component
means parts of UI grouped such that it needs the fewest amount ofstate
variables to control.The two parts of UI (exit button and app bars) mentioned in the previous point can be collectively called as a
UI component
.
"wow that makes no sense."
I understand, just move forward, and it'll be more clear. With that, I present my two cents' knowledge on the flow of a single UI component
in our app in the following diagram -
Let's consider the following scenarios -
Tap the scenario to open
Scenario One
Situation: App starts, user taps on the fullscreen button
UI component
uses thenotifier
to invoke a function on the tap event.notifier
flips some boolean propertyisFullscreen
of someWorkspaceState
totrue
in the function invoked in previous step. The wholestate
class is replaced by a new instance with the new value ofisFullscreen
property and rest properties are as they were before,UI component
automatically rebuilds to react to the new thestate
.
This was a simple scenario where there is no I/O operation involved.
Scenario Two
Situation: User is done with the drawing, now they tap the save image button
UI component
uses thenotifier
to invoke a function on the tap event.notifier
flips some boolean propertyisPerformingIOTask
of someWorkspaceState
totrue
.UI component
reacts to it by showing some loading indicator.notifier
uses someCommandManager
to get a list of all draw commands.notifier
then uses that list to generate png image using someImageService
.notifier
then uses that image data to save it to a file on the device using someFileService
.- After the file is done saving successfully,
notifier
flips backisPerformingIOTask
tofalse
. UI component
reacts to it by dismissing the loading indicator.
Enough talk, let's get to the code already!
By the time I decided on the project structure, my mentors took some time and created this incremental list of tasks. With each task is attached the link to its Jira ticket and the relevant pull request.
- Research work
- Testing (UI) PAINTROID-436
- What can we use instead of bitmaps (for performance) PAINTROID-437
- How to run iOS and Android UI Tests on Jenkins PAINTROID-438
- Drawing Board with brush PAINTROID-439 | PR
- Overflow menu: Save / Load + Basic Options (.catroid format!) PAINTROID-448, PAINTROID-450 | PR
- Fullscreen mode PAINTROID-449 | PR
- Panning/zooming for canvas PAINTROID-478 | PR
- Languages + Crowdin Integration PAINTROID-452 | PR
- Brush options PAINTROID-453 | PR
- Layers PAINTROID-454
- Colorpicker (Care for option to create AAR file for catroid) PAINTROID-455
- Undo / Redo
- More tools
- Extra options (anti-aliasing, export in .ora format, etc.) PAINTROID-451, PAINTROID-457
And here comes the end of my project period. But not the end of this migration journey! Although it is at a decent stage, there are still a lot of tasks left to make this app a worthy replacement for its android sibling. I plan to be consistently present during this process and help out other people to join in too.
A big thanks to Thorsten, Julia and Wolfgang for always helping me out and providing me with this opportunity ππΌ I am grateful to have worked with this team.
Because there are never enough links...
- Pocket Paint
- My contributions to Catrobat
- Mentors/Team
Hey! Thanks for reading. If you found this report/story/article interesting or might have some feedback, please do reach out ^_^