Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iOS - Planning #5421

Open
westnordost opened this issue Dec 20, 2023 · 4 comments
Open

iOS - Planning #5421

westnordost opened this issue Dec 20, 2023 · 4 comments
Labels
help wanted help by contributors is appreciated; might be a good first contribution for first-timers iOS necessary for iOS port

Comments

@westnordost
Copy link
Member

westnordost commented Dec 20, 2023

This ticket is something like the master ticket to coordinate development on an iOS port of StreetComplete. It replaces #1892 which also included a lot of discussion and research / observation work.

TLDR: Have a look at the Project Board for a list of tasks. Contributions are welcome!

Approach

The code base of this app is written in 100% Kotlin, a modern programming language that is quite similar to Swift. Kotlin code can be transpiled to JavaScript and also to machine code, just like Swift. Furthermore, many dependencies of this app by now are pure Kotlin libraries.

An iOS version of this app hence can be done with Kotlin Multiplatform.

The UI code can be shared using Compose Multiplatform, which is currently in alpha for iOS (= usable, but API may change). It is a fork of Jetpack Compose with largely the same API. Jetpack Compose is a reactive UI framework in which developers define the UI completely in code (similar idea as SwiftUI). We'll have to re-create the entire UI (incrementally) in Compose, though.

This will keep the amount of code that needs to be platform dependent (Android / iOS) to a minimum.

This approach has the big advantage over others in that further maintenance of this project will not increase significantly, as there will continue to be just one code base.

Steps in a nutshell

  • Separate all platform specific code from application logic. Replace Android/Java dependencies for application logic with Kotlin multiplatform dependencies

  • Incrementally migrate the UI code to Compose Multiplatform:

    1. Separate data access and UI state code from pure UI code (use view models: Use ViewModels #5070 )

    2. Migrate the UI code that currently uses Android XML layouts to Android Jetpack Compose. This can be done incrementally when done bottom-up (see migration strategy guide)

    3. Then, migrate from Jetpack Compose to Compose Multiplatform. This is a small step compared with the first one. With a little luck, Compose Multiplatform is out of alpha by then and thus the API more or less stable while the ecosystem around it is more mature.

Current State

This section will be updated continuously.

The steps necessary are described in detail in a Kanban-style Project Board. Unless stated otherwise, they are explicitly up for the taking by people who read this and (I hope 😊) want to contribute to the development towards an iOS port. If you would like to implement one of the tasks described, comment in that ticket.

The project board currently only contains tickets for stuff involving application logic, i.e. the point 1 from the "Steps in a nutshell" description from above. The other big, iterative, task that has no (individual) ticket yet is the UI part. This is because the migration to Jetpack Compose is a somewhat iterative undertaking for which I think it is better to have just one master ticket - this one (and then, many PRs). See the next comment for a more precise description.

We very roughly estimated some time ago that developing a full port to iOS would take one man-year of work. So, the scope of this remains very large. We will only realistically achieve it together with lots of contributions from the community. Luckily, this is exactly what has been happening in the last years.

So, let's shift gears!

How you can help

  • You could take over a task from the project board! Or, you could familiarize yourself with the mentioned technologies / frameworks to make meaningful contributions later. In particular: Jetpack Compose / Compose Multiplatform. This is very new to me too, so I have to find my feet here, too

  • You can sponsor development! See the title "Sponsor this project" in the sidebar on the main page. More money allows me to devote more time to development on this app in general. If you know any eligible sources for larger scale funding, talk to me!

  • You can help with maintenance and issue triage in general. Time saved on that is time I can put into furthering development on this!

@westnordost westnordost added help wanted help by contributors is appreciated; might be a good first contribution for first-timers iOS necessary for iOS port labels Dec 20, 2023
@FloEdelmann FloEdelmann pinned this issue Jan 11, 2024
@westnordost
Copy link
Member Author

westnordost commented Jan 17, 2024

On what needs to be done on the UI side of things:

TL;DR:

(Disclaimer: I (too) need to find my feet with those Jetpack Frameworks I haven't worked with yet, namely mostly Composables and Navigation. The below is a result of some research, looking at example code etc., so if I did misunderstand something I describe below, please call it out!)

Current UI architecture

current_arch

Android Activitys are mostly just containers and Fragments make up the brunt of components that define the view behavior, state, manage lifecycle, handle data (updates) and communication and navigation with/to their parent and child components.

Views are generally defined with Android XML layouts, a few "*ViewController" classes exist that define the behavior of certain view constructs in a reusable manner. Some compound/custom views may also (still) exist.

Projected UI architecture

On Android, it would look something like this:

target_arch

View layout, their behavior and their (ui) state is done via Composables from Jetpack Compose / Compose Multiplatform.

The data used for display in the UI is accessed through ViewModels. It is probably better to use Kotlin's StateFlow for the fields in the ViewModels instead of LiveData from the very start, because the former are already multiplatform.

Finally, the navigation in-between the different composables is managed using NavHost of Jetpack Navigation somehow (didn't read much into that yet). This means that the Activity is just the entry-point of the app and there are actually no Fragments left, everything is Composables and ViewModels, which means everything can be shared. (Hooray)

UI architecture steps

Now, we must avoid at all costs to open up too many construction sites at the same time. It is not feasible to go from our current architecture to the other one in one big step. Instead, we must split up the migration in as many self-contained steps that make sure the codebase stays consistent in itself:

Step 1. ViewModel

Slim Fragments: The data access and view state management shall go into ViewModels, owned by the Fragments. We have a ticket for that already: #5530. Even independently of iOS, this makes sense, as it properly encapsulates view logic from accessing data / managing state.

Step 2. Jetpack Compose

Convert the UI layout definition and behavior code bottom-up (see migration guide, migration tutorial) to Jetpack Compose. This involves not only converting the Android XML layouts to Compose code, but also the code of "*ViewController"s, RecyclerView view holders and finally of course also Fragments, as that's where most of the view behavior is currently defined.


current_view_arch drawio
(After step 1 and 2)

Step 3. Compose Multiplatform

Go the (hopefully small) step from Jetpack Compose to Compose Multiplatform. The biggest issue here will be how to access resources in a multiplatform way. There are some libraries already which facilitate this, like MOKO resources, but hopefully a popular and stable solution will have emerged by then. I really hope there will be something official from JetBrains. (They did announce this.)

Step 4. Multiplatform ViewModel / Navigation

Already by the end of step 2, Fragments will be very slim. We have the option to stop here view-architecture-wise and just duplicate the rest of the code that still lives in these Fragments on iOS in UIViewControllers defined in Swift. I think we should revisit this when we finished these steps, as these steps are really big on its own already anyway.

However, we still need to make at least ViewModels multiplatform. Interestingly, most libraries that provide some concept of ViewModels but as multiplatform also come with a solution to completely do away with Fragments (i.e. a replacement for Navigation and related things).

Those that provide a multiplatform replacement for only ViewModels are KMM-ViewModel, MOKO MVVM, Summer, kmp-viewmodel and others but most of these are either kind of alpha or older and quite inactive.

Here's a list of frameworks I had a little longer look at, all of these do something something ViewModel and also provide some glue for navigation:

PreCompose

PreCompose provides both ViewModels and Navigation whose API is pretty similar to the Android equivalent in Android. The project's readme itself mentions...

If you familiar with Jetpack Lifecycle, ViewModel and Navigation, there will be nothing to learn.

Additionally, it has some integration with Koin, which is the dependency injection framework we use.

The project exists since 2021 and is continuously active since mid-2022, 0.6k people starred it on GitHub.

Voyager

Voyager also provides both for view models and navigation.

In particular, it introduces the concept of Screens. A Screen conceptually is a replacement for a Activity or full-screen Fragment.

Instead of ViewModels, we have ScreenModels, which have the same purpose as ViewModels. Good thing: ScreenModel is just an interface. Also there is integration with Koin, the dependency injection framework we use.

Additionally, it has some setup for navigation with bottom sheets (which we use extensively) as well as tab navigation (used in profile screen).

The project exists since end of 2021 and continues to be very active, 1.8k people starred it on GitHub. Version 1.0.0 was released in December 2023 and shall bring API stability, the documentation is quite good. Of the three projects I've looked at, it is the youngest.

Decompose

Decompose can also serve as a replacement for the functionality described, although of the three libraries, it brings the greatest departure from the terminology, concepts and APIs from Android Jetpack:

  • Components are what I understand to be the replacement for the logic/navigation part of Fragments/Activities, as they optionally can have an associated ComponentContext (~ViewModel) if they need to be lifecycle-aware, keep their state during config changes and handle back button events
  • Values are observable fields within a component, pretty much what *LiveData or StateFlow would be
  • Child Stack would be the replacement for the FragmentManager, i.e. something something navigation
  • ....

One feature of Decompose is that it works well not only with Compose but also with SwiftUI and other UI frameworks, but I think we do not need that. But it means that Decompose is designed to be more of a general framework rather than being made for or tied to any specific UI framework.
Instead, it is written that Decompose goes well with MVIKotlin, another framework by the same author.

The project exists since 2020 (thus the oldest) and has been continuously very active (most active of those compared) ever since, 1.7k+ people starred it on GitHub. They are working on a new major version since at least December 2023. Striking is the extremely low number of open issues.

Conclusion

To be honest, I didn't really grasp the concepts of Decompose and the independency from Compose Multiplatform is nothing we would require. The migration to ViewModels + Composables + Navigation seems to me is already enough to chew, it does not help to then learn even new (even if related) concepts.

This is why I currently slightly favour Voyager, because just as PreCompose, it sticks quite closely to the concepts from Android Jetpack while at the same time is more popular and has more documentation + useful components than PreCompose has to offer, apparently.

The final decision on this lies somewhat in the future, though. The goal of this preliminary research has been to first ascertain whether the concepts of ViewModel + Navigation can be transferred to multiplatform too, so that the iterative steps 1 & 2 (before 3 & 4) are possible. The outcome: Yes, it's possible, frameworks exist whose concepts and APIs are quite similar to what we have with Android Jetpack.

@susrisha

This comment was marked as off-topic.

@westnordost

This comment was marked as off-topic.

@susrisha

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted help by contributors is appreciated; might be a good first contribution for first-timers iOS necessary for iOS port
Projects
None yet
Development

No branches or pull requests

2 participants