Objects with Composables!??
In a lot of my testing and examples, I tend to wrap code in objects. You won't see this in production so it may come as a bit of a shock.
I'd like to briefly explain why I do this. I can then link this to all my tutorials so people don't get too confused (thank you if you are coming here from one of those).
There are three main advantages to using objects: It's better, safer, and faster! I realize I'll need to justify those three.
This should be a friendly format for people of all experience levels, but there may be code that newer programmers don't understand. That should not affect the message.
All final GitHub Gists are linked at the end of this post if you find those easier to read.
Standard Practice
Throughout most projection apps, everything is structured like this:
Standard1.kt
/**
* Internal can be accessed within this package (whyuseobjects)
* If we make a new module, it would not be able to access this
*/
@Composable
internal fun TitleBar(title: String) { } // Imagine implementation inside
/**
* Private can only be accessed within this file.
* We cannot access this from MainActivity, for example.
* This does not pollute the global namespace
*/
@Composable
private fun BodyText(text: String) { } // Imagine implementation inside
/**
* Public (no modifier) can be accessed from anywhere in the project.
* MainActivity and other files can call this.
* Can be accessed from other packages (modules) if imported
*/
@Composable
fun StandardApp() { } // Imagine implementation inside
/**
* We mark the preview private so it doesn't show up outside of this file
*/
@Preview
@Composable
private fun StandardAppPreview() = StandardApp()
Everything would exposed to the global namespace, but we are using private, internal, and no modifiers (public).
- public (no modifier) — anywhere
- internal — same package only
- private — same file only
Problems?
Concrete Problem Examples
Copying Files
Adding
data class UiState(
val message: String = "",
val showMessage: Boolean = true
)
@Composable
private fun MssageHost(uiState: UiState) { } // Imagine implementation inside
data class UiState(
val message: String = "",
val showMessage: Boolean = true,
val onDismiss: ()-> Unit = {} // Imagine implementation
)
private data class UiState(
val message: String = "",
val showMessage: Boolean = true,
val onDismiss: ()-> Unit = {} // Imagine implementation
)
@Composable
private fun MessageHost(uiState: UiState) { } // Imagine implementation inside
Summary - No Thanks!
Object Version
All of these problems can be avoided with one extremely simple change. Wrap everything in an object. That's it. It's a super simple solution.
Once you are happy with the code and want to move it to production, just cut and paste out of the object.
I strongly recommend this pattern when you are not sure what your final code will look like, which for me is pretty much every time I start a new project and need to write code that does something unique.
If I've done it before, I can usually just open the old project and copy and paste my previous solution.
So let's see what it looks like:
Object1.kt
object Object1 {
@Composable
internal fun TitleBar(title: String) { } // Imagine implementation inside
@Composable
private fun BodyText(text: String) { } // Imagine implementation inside
@Composable
fun ObjectApp() { } // Imagine implementation inside
}
@Preview
@Composable
private fun ObjectAppPreview() = Object1.ObjectApp()
Now, we just copy that file to Object12.kt. We also add our missing UiState and the associated MessageHost() screen.
Note: We do have to do one manual change - we have to change where the preview points (so make sure it points to Object2 instead of Object1). Even that annoys me, but it is far better than the previous situation.
object Object2{
private data class UiState(
val message: String = "",
val showMessage: Boolean = true,
val onDismiss: ()-> Unit = {} // Imagine implementation
)
@Composable
internal fun TitleBar(title: String) { } // Imagine implementation inside
@Composable
private fun BodyText(text: String) { } // Imagine implementation inside
@Composable
private fun MessageHost(uiState: UiState) { } // Imagine implementation inside
@Composable
fun ObjectApp() { } // Imagine implementation inside
}
@Preview
@Composable
private fun ObjectAppPreview() = Object2.ObjectApp()
Now comes the really nice part. Copy Object12.kt to Object13.kt. Remove the onDismiss action from UiState. Fix the preview. Done. That's it. No horrible name conflicts. Nothing to worry about.
Remember our TitleBar() is internal? We can instantly swap this out in any of our test projects with a simple qualifier:
@Composable
fun ObjectApp() {
TitleBar("This Object Title")
Object2.TitleBar("Object2 Title")
}
This allows us to rapidly prototype and compare different versions when needed. Having the object namespace right in front of it also make it obvious where the resource is coming from.
In a lot of my blog tutorials, I will often have a Shared object or something similar to make it clear when code is being reused by multiple examples.
Let's also go back to our MainActivity.kt and see how that looks.
setContent {
WhyUseObjectsTheme {
// scaffold etc.
StandardApp()
StandardApp2()
StandardApp3()
Object1.ObjectApp()
Object2.ObjectApp()
Object3.ObjectApp()
}
}
Hmm, not bad. We also have one more secret advantage. We can rename the App entry points within the objects to make it clear what is being tested:
Object1.BasicExample()
Object2.WithOnDismissUi()
Object3.SimpleUiState()
You don't have to do that, of course, but it gives some insights into the possibilities.
My Original Claim
- Better - Very easy to use. Reduces stress and irritation.
- Safter - Hard to mix anything private within an object. Preview is the only real risk as it is outside of the object.
- Faster - By being easier to use and safer, you spend less time trying to figure out why things aren't working.


Comments
Post a Comment