My second go at learning some more about Kotlin Multiplatform is really not so much going to be about KMM. This post will be about using Jetpack Compose and SwiftUI to create a simple login view that I can later hook up to some shared Kotlin business logic. So let us get into it again 😎.
Jetpack Compose (Android) 🤖
I would say I am more familiar with how to create android applications so this won't be too uncomfortable for me. I have, however, very minimal experience using Jetpack Compose to create any sort of UI so there might be a bit of a learning curve.
I started off by adding the necessary package implementations in my android project gradle file to support.
- 🚀 Jetpack Compose (with activity support)
- 🎨 Material3
- 🎼 Accompanist package (to help color the app/navigation bars)
- 🖼 Material Icons
dependencies {
implementation(project(":shared"))
implementation("androidx.appcompat:appcompat:1.4.2")
implementation("androidx.compose.ui:ui:1.2.0-beta03")
implementation("androidx.compose.runtime:runtime:1.2.0-beta03")
implementation("com.google.accompanist:accompanist-systemuicontroller:0.17.0")
// Tooling support (Previews, etc.)
implementation("androidx.compose.ui:ui-tooling:1.2.0-beta03")
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation("androidx.compose.foundation:foundation:1.2.0-beta03")
// Material Design 3
implementation("androidx.compose.material3:material3:1.0.0-alpha13")
implementation("androidx.compose.material3:material3-window-size-class:1.0.0-alpha13")
// Material design icons
implementation("androidx.compose.material:material-icons-extended:1.2.0-beta03")
// Integration with activities
implementation("androidx.activity:activity-compose:1.4.0")
}
After setting up the gradle file I worked on the colors/theming files needed for Material3. I made use of the handy material theme generator to generate color and theme kotlin files. After exporting the files I ended up with a composable theme to use at the root main activity.
@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
val systemUiController = rememberSystemUiController()
systemUiController.setSystemBarsColor(colors.primary)
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}
Now getting to work on the actual login screen was pretty straight forward. I created a new file to declare my view code. I went with just two outlined inputs and one button, not going too crazy with the styling. This is how my screen turned out.
The styling is cookie cutter since most of it is out of the box Material3. The trickiest part about this view was the view password button but this was solved pretty easy though with some google-fu:
var password by rememberSaveable { mutableStateOf("") }
var passwordVisible by rememberSaveable { mutableStateOf(false) }
....
OutlinedTextField(
value = password,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp, 5.dp),
onValueChange = { password = it },
label = {
Text(text = "Password")
},
leadingIcon = {
Icon(Icons.TwoTone.Password, contentDescription = "Password input icon")
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
visualTransformation = if(passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val icon = if(passwordVisible) Icons.TwoTone.Visibility else Icons.TwoTone.VisibilityOff
val description = if (passwordVisible) "Hide password" else "Show password"
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(icon, contentDescription = description)
}
}
)
Overall I think this is a very nice looking very simple login screen. I enjoyed the experience creating UI code and not using XML android layouts. The preview functionality was also very quick and saved me from reloading the app a ton. Now onto iOS...
SwiftUI (iOS) 🍎
As mentioned previously, I have very little experience with native iOS development let alone SwiftUI. So I went with a more straight forward approach to the login page and did not use any third party dependencies for styling or theming.
For simple color styling I found a very helpful article which explained how to utilize light/dark colors.
I created a Colors.xcassets
file and used the color codes from the material 3 theme generator above to plug in here ending up with the following result. I didn't get too crazy with it 😆.
After I had the colors I created a new SwiftUI file and got to work on the login view. Some specific styling I needed to apply was:
- Outlined text fields
- Icons in the text fields
- Password view button
- Capsule like primary button
To get an icon on the left of the text field was easy. I put an Icon and a Textfield in an HStack 😅. To get the rounded outline I had to apply an overlay to the HStack and ended up with something like this.
HStack {
Image(systemName: "person")
TextField("Username", text: $username)
}
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 10)
.strokeBorder(Color("Primary"), style: StrokeStyle(lineWidth: 1.0)))
For the primary button I had to clip the button shape and they even have a preset style for Capsule clip shapes.
Button("Login", action: {})
.frame(minWidth: 0, maxWidth: .infinity)
.padding(10)
.foregroundColor(.white)
.background(Color("Primary"))
.clipShape(Capsule())
In both code snippets you can see me making use of the Color("Primary") from the color asset catalog file from earlier.
Now onto the hardest part, the view password button. This was very similar to the Jetpack Compose example. All we needed was to keep track of the toggled state and show and hide UI elements based on the state.
@State private var password: String = ""
@State private var isSecured: Bool = true
....
HStack {
Image(systemName: "lock")
if isSecured {
SecureField("Password", text: $password)
} else {
TextField("Password", text: $password)
}
Button(action: { isSecured.toggle() }) {
Image(systemName: self.isSecured ? "eye.slash" : "eye").foregroundColor(.black)
}
}
.padding(10)
.overlay(RoundedRectangle(cornerRadius: 10)
.strokeBorder(Color("Primary"), style: StrokeStyle(lineWidth: 1.0)))
At the end of the day this is the screen that I ended up with.
The inputs are not as nice looking as android, but i'm sure if more time was spent we could figure out something a bit more fancy. I think I achieved the goal though so I will leave it for now.
Overall thoughts 🤔
Comparing this experience to Xamarin I would say it just feels right and let me explain my opinion a bit more.
- Compose and SwiftUI declarative UI feels more intuitive than XAML
- The preview functionality was a way smoother experience than "Hot Reload", I had zero problems previewing either screens
- I could easily add functionality like a trailing password view button that in XAML usually ends up with either a custom view or custom renderer. Just a ton more code to implement 🫤.
- Lastly, you just get the feeling that you have all the tools in the toolbox when working natively as opposed to XAML.
Now obviously this was a very simple page and I could run into more issues later on but I am pretty happy with the experience so far.
Next part I will attempt to wire up some business logic using MVI Kotlin and Decompose multi-platform libraries. I've usually used MVVM architecture but MVI looks interesting and this project is really all about learning things I don't really know all that well.
As always you can find the full code here on my github 🧑💻
Top comments (0)