I ended up not entering properly in the challenge to win the prize, but I have decided to complete it anyway to learn be able to describe it here, so you can understand my way of thinking and how I have achieved my solution.
- Android Dev Challenge Week 3 Link
- GitHub Repository
- On the mockups there were specific Dp padding values, but since I wanted to achieve a fully responsive result I opted to use with weights and fractions (portions of a size). This way, the positioning will be common to all screen sizes.
- To control navigation between screens, I have used Compose Navigation
- To load images more effectively and efficiently, I have used Accompanist Glide
1st screen — Welcome:
So, here, the first challenge I had was to position both background images (the leaf, and the white part) in the correct place on the screen.
I have tried different ways to do it:
- First I tried to play with a single Box composable and with its content alignment, and placing the images there — it did not work
- Then, I tried Modifier methods “fillMaxSize”, “fillMaxHeight” and “fillMaxWidth”. All of these are measured according to the parent size, and receive a fraction parameter, which would definitely help me on this case.
By playing with this fraction parameter, some scales and offsets, I managed to get it perfect and responsive on all kind of screen sizes.
I was given dark and light icons respectively, so I made use of the MaterialTheme.colors.isLight variable to decide which icon to use.
The result until here was pretty good:
The next step was to add the logo and the buttons.
- I added a Box that fills the full screen size and aligns all of its content to the bottom center.
2. Inside that Box, I added another Box which has half of the parent height.
3. Inside the last Box, I have added the Column which contains the logo, the text and the buttons.
Since the LoginButton composable in Welcome screen has the same UI decoration as in Login screen, I separated it to a new composable:
2nd screen — Login:
The second screen didn’t have any major difficulties.
I have added a Surface to display the correct background color, a Box filling the max size of the screen, and aligned all of its content to the center.
The content is a Column, with the respective horizontal and vertical alignments and arrangements to the Center.
Since the email and password text input had a similar UI decoration, I have created a custom composable called “BloomTextInput”.
The most recent way to hide or show the keyboard inside a composable function is to use “LocalSoftwareKeyboardController.current”, however, to use it we need to include the “@ExperimentalComposeUiApi” annotation every place where it is used, until the top of the “hierarchy” where the function is called.
To respect the mockups I needed to set some specific colors on the “colors” argument of the OutlinedTextField.
To get a better UX, I have also used “keyboardActions” argument to override the keyboard onDone button (check button on bottom corner) behaviour to hide the keyboard and remove the view focus.
Here is the result:
3rd screen — Home:
On this screen, I decided to start on the bottom bar.
My rule of thumb is to, whenever I need to add a BottomAppBar or TopAppBar, I use a “Scaffold” composable. It places everything in the correct place, just by passing them on the respective “bottomBar” and “topBar” arguments.
Scaffold implements the basic material design visual layout structure.
This component provides API to put together several material components to construct your screen, by ensuring proper layout strategy for them and collecting necessary data so these components will work together correctly.
So, my layout will be like this at the begin:
To make everything more readable, I separated the BottomBar to another composable function.
Here I have used the BottomAppBar composable, and I have used a Row as a host of the content (horizontal orientation layout), with the necessary arrangements and alignments. Inside, I have inserted the four items as in the mockup, differentiating the one that is selected.
Each item is a Column with everything aligned to the center, which will contain an Icon and a Text.
The Icon and Text color, and the Text style are changed depending if the item is marked as selected or not.
LocalContentColor and LocalTextStyle are ways to get the correct content color and text style according to the parent composable.
After the BottomBar, I proceeded to the Search text input, and I “used and abused” the Scaffold composable. Nothing makes strictly necessary to use a TopAppBar composable inside topBar argument of the Scaffold, so I simply placed my Search bar over there.
Here I made use of the previous custom composable BloomTextInput, and just added the leading icon and the correct label.
Now let’s proceed to the content.
— — — — — — — — — — — — — — — — — — — — — -
As an aside of the Home screen, I need to show you how did I send all of the HomeGarden mock data to the UI:
Since we want to display a list of HomeGarden entities, I have created a data class to represent it.
data class HomeGarden(
val name: String,
@DrawableRes val imageRes: Int
To send the all the necessary HomeGarden entities to the UI, I have created a repository as following:
The method getHomeGardenList() returns a flow containing the list of HomeGardens.
An asynchronous data stream that sequentially emits values and completes normally or with an exception.
I have also created a ViewModel, which is responsible to communicate with the UI, and a ViewModelFactory to inject the repository on it:
This way, I was able to instantiate the ViewModel on my main App composable the following way:
val viewModel = viewModel(
factory = HomeGardenViewModelFactory(HomeGardenRepository())
I have structured the main App composable as demonstrated below:
After instantiate my ViewModel, I pass it as an argument to the Home composable.
Now, back to the Home…
— — — — — — — — — — — — — — — — — — — — — -
I get all HomeGarden entities by collecting the viewModel.homeGardens flow using “collectAsState”, and I send it to both “BloomCardRow” and “BloomImageList” to fill the lists.
Collects values from this Flow and represents its latest value via State. Every time there would be new value posted into the Flow the returned State will be updated causing recomposition of every State.value usage
Unfortunately I was not able to get a shadow as in the mockups using only the Card composable, so I had to put it inside a Box.
For the sake of reducing complexity, to display the vertical images list I have simply added them with a forEach loop, since the scroll will be already included in the Scaffold content:
Okay, my longest article until now.
Anyway, it would not be a full overview if it wasn’t at least this long.
I hope you have enjoyed it!
Once more, I’m totally open to critics and suggestions.