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 version #1892

Closed
westnordost opened this issue Jun 11, 2020 · 52 comments
Closed

iOS version #1892

westnordost opened this issue Jun 11, 2020 · 52 comments
Labels
help wanted help by contributors is appreciated; might be a good first contribution for first-timers

Comments

@westnordost
Copy link
Member

westnordost commented Jun 11, 2020

Edit: Planning for an iOS version continues in #5421

See also the last comment on this ticket


Quite a few times, it has been requested that an iOS version of this app is made available. This ticket shall serve as an explanation why this isn't possible but also as an entry point for why it is perhaps possible after all, but with enormous effort. So if you are an iOS developer and interested in creating an iOS version, also read this.

It's not possible

StreetComplete is developed as a native Android app and iOS is a completely different platform. There is no compatibility at all. Usually, if you see an app that is both available for Android and iOS, it is either not native (for example, just a website being displayed within an app or using a cross-platform framework such as ReactNative) or you are looking at two completely different applications, likely developed by two teams. This is my experience as a professional app developer.

Maybe it's possible

So, this is interesting if you are an iOS developer yourself. It is not planned from my side to do this.

Kotlin Multiplatform Mobile

This app is written in Kotlin, a modern programming language that is quite similar to Swift. Originally, Kotlin was built to run on the JVM, but by now, it can be transpiled into JavaScript, and with Kotlin Native, also to machine code, just like Swift.
This makes it possible to write iOS apps with Kotlin. Thus, Kotlin code of this project that is not dependent on any Java library (Http library etc) and also not dependent on the Android platform (UI, sensors, persistance management etc) could be used 1:1 in an iOS port of this app.
Such "pure" Kotlin code would be put into a common module that is shared between the iOS and the Android app.
However, not a big percentage of the total code in this project is such pure Kotlin code that has no dependencies to any other Java or Android dependencies. Most of the iOS app would still need to be written from scratch, in Swift or better in Kotlin, if possible. On the other hand, some of the code that is currently dependent on some Java library or Android dependency could be changed to not be dependent on it directly anymore but on a wrapper interface. Then, only that wrapper would need to also be implemented for iOS.

Multi-OS Engine

The MOE is a little different approach. So, the JVM of Android is called ART and is open source. On Android, we write apps that run within this virtual machine provided by the Android system. For iOS, there is no such virtual machine, apps run natively. But, what we could do, is to write an app that comes bundled with the ART virtual machine and does nothing else than to load the provided JVM bytecode. This is the approach of MOE.
In other words, ART is automatically bundled in the app so that one can write an iOS app in the Java environment, making use of the Java ecosystem and libraries. iOS platform dependencies are accessed over an interface provided by MOE.
The difference to the Kotlin Native approach is, that Kotlin code that depends on Java libraries can also be moved to the common module that is shared between the iOS and the Android app. The only things that cannot be shared is code that is dependent on the Android platform, such as UI or currently persistance with SQLite (because it uses Android dependencies)
.
(see #1892 (comment))

@westnordost westnordost added the help wanted help by contributors is appreciated; might be a good first contribution for first-timers label Jun 11, 2020
@peternewman

This comment has been minimized.

@RubenKelevra

This comment has been minimized.

@westnordost

This comment has been minimized.

@RubenKelevra

This comment has been minimized.

@wtimme

This comment has been minimized.

@westnordost

This comment has been minimized.

@jscheffl1
Copy link

Hi thanks for the clarification! I was already seeking for an alternative option to be able to contribute to OSM on my iOS - I was quite happy with my legacy Android phone to gain points and correct small glitches.
Unfortunately I'm not an iOS developer but very probably the efforts are enormous. But anyway I can also say there is demand :-)

Anyway THANKS for the efforts in the Android edition, I really like the app!

@westnordost
Copy link
Member Author

westnordost commented Sep 3, 2020

@wtimme and I analyzed in a little more detail what needs to be done.

1. What needs to be done completely from scratch

Anything view- and Android-related needs to be done from scratch. In a little more detail with very rough (week-wise) estimations:

  • The complete about screen (1W)
    • Parsing and display of changelog
    • Display of privacy statement, credits, ...
  • The complete settings screen (1W)
    • Including saving/retrieving options from iOS UserDefaults
    • Manage dark mode
  • The complete My profile screen
    • Login screen (0.5W)
    • Profile screen with avatar downloader and display, statistics downloader and display (2W)
    • Quest statistics screen with "ballpit view" and "by quest type"/"by country" sections, quest type info dialogs and country info dialogs, including having all the country flag icons as graphics that can be displayed on iOS (3W)
    • Achievements screen, including the graphics for these and the achievement info dialog (1W)
    • Link collection screen (0.5W)
  • The complete map view
    • notification button and notification dialogs (new OSM message, new achievement dialog, new version dialog) (0.5W)
    • location pointer pin (0.5W)
    • star counter, compass view and compass sensor stuff, undo button and dialog, upload button, download button, location button, zoom controls, main menu (2W)
    • download progress button (0.5W)
    • use tangram-es, have thick tangram-es convenience wrapper (2W)
    • generation of quest sprites (0.5W)
    • manage display of quests on map, tapping on quests, highlighting quest geometry, returning to map etc (1.5W)
    • manage location and accuracy markers, map gesture controls (1W)
    • manage quest-specific markers, f.e. for splitting the way (0.5W)
  • Quest download background service with notification - only the setup and management of the service, not the download as such (0.5W)
  • Changes upload background service with notification - only the setup and management of the service, not the upload as such (0.5W)
  • All forms for all the quest types. Some forms are used for several quest types (>5W)

So in total about 5 months or more. Some things (~2 months) are not so important for the core functionality and can be added later or bit by bit.

2. Things that can be shared but must be adapted to work

These are things that are pure Kotlin but currently have Android and no Java dependencies. Most classes that are in the /data/ directory. All DAOs to access the database, almost all controllers (classes that manage DAOs), download and upload logic, metadata, data, diff data, filters parser (similar to Overpass Wizard syntax) and all the quest types.

  • SQLite dependencies implemented on iOS side (1W) for the DAOs to work
  • Quest types use Android resources (for icon, title), these must be outsourced from the actual quest type definitions (1W) and then redefined for iOS (1W)
  • Quests use data classes that use Android resources (for icon, title, ...), these must be redefined for iOS (1W)
  • QuestAutoSyncer depends on Android location framework and must be wrapped (at least), other location-related stuff (1W)
  • Logging in certain classes uses an Android framework. Should switch to an own implementation or wrap it in an interface (1.5W) (see also Add in-app log viewer #3597)
  • SharedPreferences must be wrapped behind an interface so that UserDefaults can be used instead for iOS (maybe https://github.com/russhwolf/multiplatform-settings could be used) (1W)
  • More/all constants defined in ApplicationConstants should be injected instead (1W)
  • the osmapi library + oauth stuff + overpass extensions is a Java library and must be replaced with an iOS implementation (6W)
  • the country boundaries library is a Java library and must be replaced with an iOS implementation (2W)
  • opening hours parser is a Java library which needs to be replaced by an iOS implementation (3W)
  • version is banned checker uses Java dependencies (0.5W)
  • Avatars download and note image upload uses Java dependencies and must be reimplemented on iOS (1W)
  • Controllers (etc.) use CopyOnWriteArrayList to manage listeners, must replace with something else that is not Java (0.5W)
  • Abbreviations and CountryInfos are in Java (because they are candidates to become libraries) and must be replaced in iOS with an own implementation (2W)
  • The feature dictionary is a Java library and must be replaced with an iOS implementation (2.5W)

So, about 6 months or less (~1-3 months less) if one just ports/copypastas the used java libraries into swift code or finds very similar ready-made libraries that do the same.

@westnordost
Copy link
Member Author

westnordost commented Sep 6, 2020

As far as a minimum viable product is concerned:

  • Much from point 1 can be left out. An MVP could not offer any settings, any about menu, any statistics or profile view, no note discussions, no advanced interactions with the map view (split way) etc.. The only thing that is indispensable is the map view with the quest pins and the quest form for those for which it is implemented. So for the MVP: One quest type at least
  • Stuff from point 2.1 can not really be left out
  • From 2.2:
    • the osmapi library cannot be left out. Same with wrapping of its data types, the version is banned checker, the cancel-flag and the CopyOnWriteArrayList, the usage of Date in the filters syntax and date-based functions
    • country boundaries library is only necessary for certain quest types (those that have any country-specific logic or exclude certain countries), so can be added later
    • the opening hours parser is only necessary for the opening hours quest, can be left out first
    • avatars download and note images upload is only necessary for notes and optionally. Could be left out first.
    • Abbreviations are only used for the AddRoadName quest, so if this is left out, the abbreviations are not immediately needed
    • CountryInfos are only needed for certain quest types, could be left out at first
    • The feature dictionary is only used by certain quest types, could be left out first

So, the estimation for a MVP could be as little as 2 months for UI and 2 months for the core, so in total 4 months.
If Kotlin Native is chosen over Multi-OS-Engine,
adaptions to the core would take 4.5 months, so in total 6.5 months.
This is not so unachievable anymore, especially if multiple people would be working on it. I also remember working on the app about half a year before I could release the first public version. So, if any iOS developer who is interested in it reads this but wouldn't want to stem this alone, give a shout!

@westnordost

This comment has been minimized.

@oliviernguyenquoc

This comment has been minimized.

@matkoniecz

This comment has been minimized.

@westnordost

This comment has been minimized.

@westnordost

This comment was marked as outdated.

@matkoniecz

This comment has been minimized.

@westnordost

This comment has been minimized.

@matkoniecz
Copy link
Member

I was thinking about plans a bit and I am wondering whether things from

2.1 However, the following changes need to be made:

are worth doing right now. How likely is that work done now (without iPhone etc) is going to be useful and get us closer to potential iPhone version?

Is any of this things useful on its own, or is it just making things not worse on Android and being a small piece of work toward iPhone version?

Is it worth doing them just in case and to make iPhone version a tiny bit closer?

For example in

Logging in certain classes uses an Android framework. Should switch to an own implementation or wrap it in an interface (1.5W)
SharedPreferences must be wrapped behind an interface so that UserDefaults can be used instead for iOS (1W)

it seems that creating intermediate interface should not be something horrible complicated. But does it make sense to do it right now, given that it will be a bit more confusing for people used to Android development?

@westnordost
Copy link
Member Author

Some things make sense, because they don't make the code more complicated. Some others would only make sense if one really started to work on an iOS port because it would make the code more complicated. I'd say, these things could make sense on their own:

  • Logging in certain classes uses an Android framework. Should switch to an own implementation or wrap it in an interface (1.5W)

would make it possible to (also) log the logs to some place within the app, so that the log could be attached to crash reports and/or accessed by the user when asked about it.

  • SharedPreferences must be wrapped behind an interface so that UserDefaults can be used instead for iOS (1W)

That would be cleaner anyway, in my opinion. Maybe it is two things:

  1. logic classes should not have SharedPreferences as dependency directly (just as they should not depend on the database directly)
  2. logic classes should not have access to the whole shared preferences but only through something like for example LastEditTimeStore

@westnordost
Copy link
Member Author

MapLibre (ex Mapbox SDK) now supports Metal, so an iOS port would probably (need to) use MapLibre instead of tangram-es. This requres to

  • create a mapstyle for mapbox / maplibre (for jawg.io map tiles) that matches the map style for tangram
  • check it out, see if it has all features required by StreetComplete (probably yes)

As tangram is not really developed further, it might be a good choice to change to using the maplibre SDK for Android too. Or - bluesky - use/create a map view that directly renders the OSM data.

@westnordost
Copy link
Member Author

Might want to monitor https://github.com/icerockdev/moko-mvvm - would, when using MVVM (the app currently doesn't though), make it possible to put even the ViewModel code into the common code. (Was mentioned in the Kotlin Multiplatform Mobile Beta Roadmap Update 2022-05-31)

@westnordost

This comment was marked as outdated.

@FloEdelmann

This comment was marked as outdated.

@westnordost

This comment was marked as outdated.

@YoshiRulz

This comment was marked as outdated.

@westnordost

This comment was marked as outdated.

@YoshiRulz

This comment was marked as outdated.

@HolgerJeromin
Copy link
Contributor

https://blog.jetbrains.com/kotlin/2022/10/kmm-beta/
https://android-developers.googleblog.com/2022/10/announcing-experimental-preview-of-jetpack-multiplatform-libraries.html?m=1

And a well known german news site announcing this beta with some explanation:
https://heise.de/-7304527

@YoshiRulz

This comment was marked as outdated.

@westnordost

This comment was marked as outdated.

@susrisha
Copy link

susrisha commented Aug 8, 2023

I run a team of mobile developers who are into both iOS and Android. Would love to give this one a try.

@westnordost
Copy link
Member Author

westnordost commented Aug 9, 2023

That's great to hear!

Let me summarize for you the current state of affairs:

Kotlin Multiplatform Mobile (KMM)

A possible iOS port should be developed using Kotlin Multiplatform Mobile so that as much code can be shared as possible. (Yes, the amount of sharable code is considerate. Already 2-3 attempts at creating an iOS port from scratch were abandoned because they all underestimated the sheer size of the project.)
In a nutshell: Application logic shall be implemented in pure Kotlin without direct dependencies to Java or Android. This code can then also be used for the iOS part. Currently, most of that code is already free of such dependencies.

The next self-contained steps (I can think of) in that regard are, for which PRs are welcome:

1. Get rid of Java and Android dependencies in application logic (=non-UI) code

  • small Controller classes use Java's java.util.concurrent.CopyOnWriteArrayList to store their listeners (observer pattern) which need to be replaced to get rid of the dependency to Java. (See iOS version #1892 (comment)) done by @neonowy in Get rid of CopyOnWriteArrayList #5346

  • big At some places java.util.Locale and related (e.g. java.text.DateFormatSymbols) are used for different things which need to be replaced. This might be a bit difficult because as far as I know, there is no multiplatform replacement for Locale (yet?). So, either the Locale functionality used in different places needs to be re-implemented or someone would need to create a Kotlin multiplatform library that is a 1:1 replacement of Locale. Either way is going to be a considerate amount of work, I think.

  • small PolylinesSerializer uses various Java *Stream classes which need to be replaced. Not sure if there is an easy replacement (but there should, anything that deals with I/O needs something like this, and e.g. ktor is multiplatform, as far as I know) done by @neonowy in Migrate PolylinesSerializer to kotlinx-io #5307

  • medium Some DAO classes use SharedPreferences (Android's persistent key-value store) to store certain data. That class should be wrapped in an interface so that it can be used independent of platform. (There's also multiplatform-settings library but not sure if it is worth it, considering it should just be some 100 lines of code) done by @neonowy in Wrap SharedPreferences behind Preferences interface #5357

  • medium Some controller classes use the Android logger to log stuff. This has to be replaced either with a thin multiplatform wrapper (see also Add in-app log viewer #3597) or use a multiplatform logging framework such as Napier, Kermit, Kodein-Log, or others .... The app doesn't log that much though, so an elaborate logging framework might be over the top. done by @neonowy in Add in-app log viewer #5335

  • big the app depends on the Java library osmfeatures (made by me). It needs to be migrated to Kotlin multiplatform library (or ported to native and wrapped).

  • big the app uses the Java library osmapi (made by me). It must be replaced or migrated to multiplatform somehow... maybe another approach is better with some auto-generated API through a description of the API (maybe via swagger or similar? -maybe check this out) . Or, could this be done with retrofit? If yes, maybe it could be done with Ktorfit

  • none / wait the app uses the YAML parser com.charleskorn.kaml which is currently JVM only, however it looks like there is some work in progress to make it work on native, too. Alternatively, this could be one chance to get away from YAML to e.g. TOML or JSON for config files (we use only very basic YAML features anyway, TOML would be more than enough)

  • medium the app uses the Java library OpeningHoursParser to parse opening hours (, collection times, etc.). This needs to be wrapped and replaced for the iOS app. Not sure if there is a library for this, but GoMap!! also parses opening hours, so bryceco needs to have some way to do this. The linked library is created with JavaCC, so even the EBNF-like notation is Java-specific. For a Kotlin multiplatform version of such a parser, maybe ANTLR or more specifically, https://github.com/Strumenta/antlr-kotlin could be used

2. big (once most application logic is free from Java/Android deps) create a Kotlin Multiplatform Mobile project setup and move the pure kotlin code to the shared module.

  • medium migrate also the unit tests to the shared module. Many tests use Mockito, which would need to be replaced with a mocking-library that isn't Java specific, such as MockingBird, Mockative or MockMP. (Needs research which mocking library is the best choice)

  • big (finally) the quests themselves have some dependencies to Android. This is currently convenient, so I would not break that up until the other things that block an iOS port are done beforehand. (Also I have to think about that before how to exactly do this best)

(If I see there is some commitment, I can also write these into tasks with more precise descriptions, rough time estimations, the github kanban-like board etc. later)

Compose Multiplatform

The Android UI code is currently done with layout XMLs. In KMM, by default, one would write the iOS specific part (mostly: the UI) in Swift like in other iOS projects and access the application logic as a library. This means that the entire UI for iOS has to be re-written from scratch and also - importantly - having to be maintained separately from the Android UI.
An alternative to this is Compose Multiplatform, built on top of KMM, currently in alpha. It is a fork / extension of Android's Jetpack Compose, a UI framework comparable to SwiftUI. In Compose Multiplatform, you write the UI once in shared code and it renders on both Android and iOS. This still means that the UI needs to be written from scratch, however

  1. in the end, there will only be one UI implementation, not two. This makes a huge difference. To expect a long-term commitment of any code contributors to also maintain their contributed code would be illusory. And as the app is not backed by any company or organization, the time the main developer (that's me) can put into app maintenance is severely limited. Maintaining two UI implementations is going to be double as time-consuming as maintaining one, i.e. not really viable if I remain to be alone.

  2. since Compose can apparently be integrated seamlessly into Android XML layout UI code and vice-versa (same with UIKit and SwiftUI, but this is not relevant here), a migration from Android XML-based UI code can be done step by step. This, too, is a huge plus as it enables different contributors to contribute their piece and come and go without one contributor in particular committing (alone) to a huge undertaking that may work or may fail entirely. I've learned that such an iterative model is key to success in an open source project where you can't oblige people to see anything to the end like it would be the case for employer-employee relationships.

The main downside of Compose Multiplatform is that we'd have to replace working code and that it is still in alpha. Working with cutting edge technology usually isn't pretty because the environment is changing under your feet and certain things don't work or only with messy workarounds. Additionally, Compose Multiplatform is not the silver bullet for the UI, as many Android/Java-only dependencies are used in the UI which would need to be replaced with an appropriate replacement on iOS. (E.g. the map renderer, the QR code generator, the physics simulation in the profile screen, ...).

However, Jetpack Compose on the other hand is stable and has an almost 1:1 API as its Multiplatform brother, so "just" migrating step-by-step to Compose doesn't seem too risky. Also, as the list in the previous heading shows, there are enough other things to do (before) anyway, so when that is done, Compose Multiplatform and the environment around it matured more.

In any case, I am currently familiarizing myself with the technology, so I might be able to say more about that in the future.

In conclusion

, there are many small tasks that can be done to further advance towards an eventual iOS port. These do not require a heavy or long-term commitment and can be included in the project as contributions just when they are done each. I know it can be somewhat demotivating to want to contribute to a project a huge project and then see that it will never be merged (because it becomes bigger and bigger along the process, a neverending construction site). For the above tasks, this is not a concern, as they are all each modular, self-contained and once merged live in the same repo and branch as the original Android version.

Working with and getting to know new (Kotlin Multiplatform Mobile is in beta) or even cutting edge technology (Compose Multiplatform is in alpha) can be a motivation on its own. Especially, I find the prospect of (learning how to use) the two technologies combined together somewhat appealing also from a business perspective, as it may potentially cut the cost of developing an Android + iOS app to almost half.

@mnalis
Copy link
Member

mnalis commented Sep 2, 2023

@HolgerJeromin
Copy link
Contributor

Kotlin Multiplatform (KMP) is making progress:
https://blog.jetbrains.com/kotlin/2023/11/kotlin-1-9-20-released/
https://blog.jetbrains.com/kotlin/2023/11/kotlin-multiplatform-stable/

sum up from a german IT new site:
https://www.heise.de/news/Kotlin-macht-Ernst-mit-der-plattformuebergreifenden-Programmierung-9352485.html

@mcliquid
Copy link
Contributor

@westnordost westnordost added iOS necessary for iOS port and removed iOS necessary for iOS port labels Dec 20, 2023
@westnordost
Copy link
Member Author

westnordost commented Dec 20, 2023

I recently reviewed what has been done by contributors to advance towards an eventual iOS version since 2021-something and came up with this extensive list of big and small contributions. It's quite a lot (, and sorry if I missed something, relation to iOS / multiplatform was often not specifically mentioned):

by @neonowy

by @YoshiRulz

by @riQQ

by @FloEdelmann

by @Helium314 , earlier @adrianclay

by @adrianclay

Thank you, guys, for all these contributions! So, I think it is time to shift gears now:

Let's stop talking about preparing stuff for a potential iOS port that may or may not be developed, but ... actually do it! Let's try to achieve a working version of StreetComplete for iOS. It could be a minimum viable product at first, and then we will move from there.

Of course, now as before, a lot of things need to be done, but I also hope that with me also having committed to it, more interested parties will help with this effort. (For now I will, for starters, read into Jetpack Compose (Multiplatform) and its best practices before I start working on the elephant in the room, which is the rework of the UI to Compose.)

As mentioned earlier, the work involves somewhat modular tasks, so it remains an easy incremental process.

I went created tickets in which I described in detail what has to be done and added them to a kanban-style project board.
Furthermore, I created a new ticket that shall replace this one, as this ticket also contains a lot of discussion and research and outdated information that is not relevant anymore.

Let's continue there:

Master Ticket: #5421

Project Board: StreetComplete/Projects/1

@riQQ
Copy link
Collaborator

riQQ commented Dec 20, 2023

There are two more PRs of mine that worked towards using kotlin test instead of JUnit:

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
Projects
None yet
Development

No branches or pull requests