Migration to Compose/modern app architecture #981
Replies: 2 comments 3 replies
|
Essentially, I think it is worth using the announcement that traditional Views are in maintenance mode as an opportunity to refactor and improve the app architecture with a view towards migrating completely to compose. This would be a long term project and far beyond the scope of a single pull request, so I think it would be valuable to put some effort into planning the transition and the ultimate end product. Hopefully in the end we can have a semi-formal plan + architecture we can point to for future contributions. ProblemsAdmittedly, I'm not yet fully across the codebase of this app, so I think other contributions would be valuable here, but one of the most obviously problematic components I can identify is the LocalBroadcastManager. The library itself has been deprecated since 2022, and I think their reasons for doing so apply to us as well:
I've modeled a real example of this use of the library to violate layering in the current codebase: sequenceDiagram
participant UI as UI (Note/Main ViewModel)
participant UC as BookSparseTreeForNote (UseCase)
participant DR as DataRepository
participant LBM as LocalBroadcastManager
participant MA as MainActivity
participant DM as DisplayManager
Note over UI, UC: Triggered by user or link
UI->>UC: UseCaseRunner.run(BookSparseTreeForNote)
UC->>DR: openBookForNote(noteId, sparseTree=true)
Note over DR, LBM: Communication
DR->>LBM: sendBroadcast(ACTION_OPEN_BOOK)
Note over LBM, MA: Delivery
LBM-->>MA: onReceive(intent) [LocalBroadcastReceiver]
Note over MA, DM: Navigation
MA->>DM: displayBook(bookId, noteId)
Note over DM: UI Update
DM-->>MA: Load BookFragment with focus on note
As you can see in this particular example, the data layer directly triggers a page change when that very explicitly should not be its concern. In my opinion, as recommended in the previously recommended deprecation notice, we should be replacing uses of the LocalBroadcastManager with either (when appropriate) more direct invocations of functionality or shared reactive state. In the case of this particular example, opening the BookFragment should be moved to the Fragment call site after the actual DB transaction performed by the DataRepository function is completed. In it's usage in the RefileFragment I would add an EventFlow which is triggered after sealed interface RefileEvent {
data class NavigateToNote(val noteId: Boolean): RefileEvent
}
// ...
class RefileViewModel {
private val _events = EventFlow<RefileEvent>()
val events = _events.asFlow(viewModelScope)
fun goTo(noteId: Long) {
viewModelScope.launch(Dispatchers.IO) {
UseCaseRunner.run(BookScrollToNote(noteId))
_events.send(RefileEvent.NavigateToNote(noteId)
}
}
}
// ...
class RefileFragment {
viewModel.events.collectWithLifecycle { /* Do navigation here */ }
}Furthermore, the use of Where I would startFirstly, as a matter of policy, I would decide that we are migrating to a shared state model as well as modern "jetpack" solutions - this includes migrating to Coroutines and Flow. To enable contributions while migration is underway, the best bet would be to write wrappers for existing legacy code we identify as problematic, such as I have done with the display manager with Navigator. For the LocalBroadcastManager specifically, can design components that purport to represent "best practice" architecture but are actually implemented using the library under the hood until we get around to fixing it. I would slowly split out repositories from the Placing data logic into the ViewModel and business logic in UseCases would be enforced and migrated towards. Considering best practices re: ViewModels and Compose, all should eventually have a single state object Flow which fully represents UI state and a Flow which represents events. My work in SavedStateViewModel demonstrates a way to do this. That's a lot of it, I would be very interested in hearing feedback, disagreement or other suggestions. Obviously I'm quite new to the project and don't have as in-depth knowledge of the codebase as I'm sure other contributors may have, so I'm very flexible re: my suggestions. |
|
Thank you for the write up. Sadly, am not familiar with the structure of the project. But as a user of the project and as an Android developer I have a few thoughts.
At an architectural level we should be aware of where do we want this project to be. And how do we enable future contributions based on the new architecture. With that I will go back and try to read the project further. |
Uh oh!
There was an error while loading. Please reload this page.
This discussion serves as a starting point to plan a long term improvement in the app architecture as well as a full migration to compose, continuing on from discussions in #866.
All reactions