I am trying to come up with as many best practices...
# android-architecture
t
I am trying to come up with as many best practices when writing native android, I think it would be great if we can come up with some “rules” for building well architected apps. These are few questions that I would wanna hear about, and please add something if it’s a good practice • Flows or suspend calls when fetching network data? • Are you wrapping network results in some kind of a Result sealed class? • Does your vm have multiple or one state variables? • When to trigger data fetching, vm init function or call vm in some lifecycle method? • VM navigation or compose callbacks?
j
Depends if you are SDK owner or doing only your own isolated app/module subset I think. • For network, if using repository or use case pattern I think strive using Flow with some kind of Result object, for higher re-usability across repos or consumers from different contexts. • Result, can use Kotlin Result class as of example, no need to write your own anymore 🙂 Depends of your needs ofc, if having lets say a nice error handling system in server always controlling, could be nice bake that logic into logging and error handling serialization in result. • I think VM should have only one state, never multiple ones. • Trigger data fetching depends from case to case, sometimes need in init if using like UDF ui state, sometimes need in lifecycle as well, could need combine them with cached state. • Whats compose callbacks, never heard of? You mean like @Composable lambdas in androidx navigation compose?
t
Thanks for advice, my bad on those Compose callbacks, I was referring to the lambdas that screen will receive as parameters that NavHost knows how to execute, for example Google Now in Android
Copy code
val navController = appState.navController
    NavHost(
        navController = navController,
        startDestination = startDestination,
        modifier = modifier,
    ) {
        forYouScreen(onTopicClick = navController::navigateToTopic)
        bookmarksScreen(
            onTopicClick = navController::navigateToTopic,
            onShowSnackbar = onShowSnackbar,
        )
        searchScreen(
            onBackClick = navController::popBackStack,
            onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) },
            onTopicClick = navController::navigateToTopic,
        )
        interestsGraph(
            onTopicClick = navController::navigateToTopic,
            nestedGraphs = {
                topicScreen(
                    onBackClick = navController::popBackStack,
                    onTopicClick = navController::navigateToTopic,
                )
            },
        )
    }
j
In the end in this topic I think it more boils down to matter of taste, rather than best practice. Every single place I went to has been different, never heard consensus in this topic ever at any company or outside. There is more trends, and overall consistency level per company, rather than the community level I think. There is some guidelines for compose, Kotlin and such however 🙂 Also got curious, any samples of VM navigation?
s
Fetching network data is a one shot thing. A
suspend
call/fun in your repo-class, service-class is the way to go (your viewmodel should 'merge' the results of these cals into a flow that your UI can then subscribe to) Networking/business-logic results: In a sum-type, yes. Like
Either
or
Result
Be sure to have your viewmodel emit only 'plain' values to your UI, so that unwrapping them does not become a concern of your ui code. Single or mutiple state vars: it depends 🙂 Ideally, single, but sometimes multiple is just a bit easier to handle in your code. Bundle discrete sets of UIs in logical and (quite independent) chunks and have a single state var to each of them. Init or lifecycle for fetching: Depends; i prefer in lifecycle changes (eg when into STARTED state). Still be sure to initialize your initial state correctly, the state before any data is fetched. VM navigation or callbacks: No opinion 🙂
🫶 1
t
j
@Teo Vladusic Dont see any difference between the cases for VM vs compose lambdas, its always composable lambdas. Just uhm different if using hilt scopes nav graphs or not in that particular library. I would enlarge the scope, how to navigate in general, if using androidx compose navigation at all 🙂
@streetsofboston You can do one shot Flows, as they are default cold Flows and not hot flows 🙂 Creating a new flow each time calling, in same way suspend is not a problem 🙂 Much easier for VM imo if all repos using Flows as much as they can, as get into annoying issues of using Coroutine Scopes when not really need to, until actually want to execute anything.
s
Yup, but a one-shot flow is the same as a suspend fun... : In your viewmodel, you can start the scope:
viewmodelScope { launch { uiStateFlow.value = suspendDataFetch(input); }}
It is indeed a bit more work, but if using a Flow is done because the UI subscribes to Flows, it's putting a concern of one layer (UI) into a another layer (Data/Repo) Also, calling sequential suspend funs reads better, imo, than chaining flows using lambdas, more imperative 🙂
But it's a bit of preference, suspend vs flow this way.
j
https://craigrussell.io/2022/03/20/should-android-repositories-expose-suspend-functions This summarize pros and cons very well 🙂 But in the end I think its a matter of taste imo and not best practice. Also using reduce and combine for Flows I prefer much more than suspend. But ofc depends what it is. In most cases I think network layer returns like Result<List<A>> and for multiple items result I think Flow make more sense. If its singular item prefer suspend, but often done above the repository layer 🙂 Anyway thats my opinion. I guess use suspend if like to enforce the consumer to do more explicit things with Coroutines.
s
I like suspend more, since my experience with Rx(Java) and trying to do flow control over Observables has been just a pita 🙂 Eg try to code out asynchronous code for 'retry' (repeat until, exponential back-off, etc) with either Flows/Observables or with suspend calls. The latter is much easier, imo... just a for or while loop around one or more suspend calls. Also, a ListT is jut one item, just with a bunch of elements inside it 🙂 But yes, i strongly prefer suspend over Flows when possible, due to my long experience with RxJava and how it has caused me a lot of headaches if conditionals/flow-control come into play. It's not a industry standard/best practice
👍 1
d
The best advice I can give you is not prescriptive. Be fast on principles and slow on rules. Rules serve to uphold principles but can lose their value as the scene of the product changes over time. Be pragmatic and strive for simplicity, but not so simple so as to sacrifice the sophistication required to achieve elegance. Be willing to do hard things. Boiler plate is not always bad, it’s a trade off of effort for flexibility. Keep in mind the meaning of Soft in *Soft*ware. Software is going to change because change is the constant. So build you systems to be flexible and adaptable to change.
👏 1
❤️ 1
Decisions that align with those platitudes always serve me well.