One thing I'm having really difficulties with comp...
# compose
p
One thing I'm having really difficulties with compose is naming. How do I name my things? Let's say I have a counter: Intuitively, I'd create a function called:
@Compsable fun Counter()
But now I need to separate the logic and UI and need to create a counter class that is the actual counter and not the ui. Intuitively I'd call that counter
class Counter
. Now having both of these named the same is obviously a bad idea, but what's the solution to this? Consistently suffixing every composable ui function as
CounterUI
? Or
CounterView
? How do you handle this?
โž• 1
a
In such cases I prefer CounterUi
p
But only on name clashes?
a
Yep
Specially when there is such a separation
c
When you say actual counter, do you mean another Composable? Or the state? If it is the latter you might consider
CounterState
.
โ˜๏ธ 3
p
I mean the state producer, not the state
And totally off topic: Did you find a way to get rid of the "rename your compose functions lower case" kotlin warning? ^^
a
I thought it is fixed in three latest AS canary, isn't it?
p
not in -14 at least
a
As a workaround you can disable it for the entire project in the settings
p
Could you do a review on https://github.com/PaulWoitaschek/Voice/commit/885d1501c283eea9d027fc55febcab928fe93b26 and tell me if that compose usage is correct like I wrote? Its my first attempt on compose and everything is new ๐Ÿ™‚
d
I postfix all my Composables with UI. And so far I pass them all a single data class post fixed with UIState
For example:
Copy code
@Composable
fun HomeScreenUI(
  state: HomeScreenUIState
) {
  ...
}
c
I have the same problem. Haven't found a good solution, but I don't really like the UI postfix. Then again, I've been struggling with naming my network, database, and domain models as well. So add this to the list. ๐Ÿ˜„
e
it's also a lot easier to deal with naming conflicts in Kotlin than in Java. if you have two different classes named Counter in two different packages and want to use both of them, you can
Copy code
import some.package.foo.Counter as FooCounter
import other.package.bar.Counter as BarCounter
and use both of them in the same file without having to fully-qualify either of them.
that, and overlap between function names (which composables are) and class names is OK as long as overload resolution can distinguish a function call from a constructor call
p
Yeah, I usually do
import androidx.compose.runtime.Composable as Compostable
๐ŸงŒ
๐Ÿ˜‚ 1
๐ŸงŒ 1
h
I recently asked a very similar question on Twitter and got a convincing answer https://twitter.com/commonsguy/status/1380535450755084291?s=19
p
Ah yes that's a pretty good idea. While refactoring, I started that XYZRow pattern anyways because it just made sense.
c
adding a suffix on the composable based on the presentation
Ooh. I do like that too. But do any of you have a Ui data class? Let's say I have a Person that has like 9 fields. And a PersonCardData class has 4 fields (some are straight mappings from Person, but some are formatted (e.g. think First Name + Get Middle Initial + last name)) Then I want to display PersonCardData in a PersonCard composable? Does that sound reasonable?
p
Yes, that's reasonable. I have a settings screen and use the row pattern there because it's a list: https://github.com/PaulWoitaschek/Voice/blob/main/settings/src/main/java/voice/settings/views/autoRewind.kt#L15
s
Copy code
public interface CounterState

// default impl with default state policy
private class CounterStateImpl: CounterState()

public fun CounterState(): CounterState = CounterStateImp()

@Composable
public fun Counter(
    state: CounterState = remember { CounterState() }
) {
   ...
}
Seems to be the default naming convention for this esp. if the class is a state-like object. You can do without the interface, but showing the full pattern as it's often useful in letting you have more control without restricting callers ability to specialize behavior.
This section covers the pattern more fully https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md#default-policies-through-hoisted-state-objects Again, the interface is optional - the main thing it wins is the ability to make a default impl without hardcoding the policy.
All that said, this is just the patterns used in the compose library, other conventions are valid. As long as you don't verb your composables, then we'll have to talk ๐Ÿ˜‚
This is also a good section to point to: https://github.com/androidx/androidx/blob/androidx-main/compose/docs/compose-api-guidelines.md#extensibility-of-hoisted-state-types Using final state classes bit us a lot in early compose APIs because it made it difficult to create single sources of truths for multiple composables. This came up in Kotlin a lot due to it's (good) preference for final classes which restricted the ability to merge things while making the type system happy. The note in the docs there is spot on though - since replacing a final class with an interface is not a source-breaking change it's a pretty cheap refactor to introduce at any point. Starting with a simple state-like class won't cause problems here most of the time. It's useful to know the full pattern when it's not enough.
One final note on the topic, we found that composable factories tended to not be the right solution to things that might be instantiated outside of compose (e.g. in a ViewModel) Instead of
Copy code
@Composable
fun makeCounterState()
Prefer
Copy code
fun CounterState()
The exception to this rule is if
CounterState
must always be remembered, you can add a convenience remember-constructor
Copy code
@Composable
fun rememberCounterState() = remember { CounterState() }
But if there's any chance you'll ever want to instantiate one outside of compose, provide another mechanism ๐Ÿ™‚.
๐Ÿ‘ 1