Created
August 21, 2023 00:42
-
-
Save BlakeBarrett/58ce9d62e510ee726b035143e4941d7e to your computer and use it in GitHub Desktop.
JSON USER DATA EXAMPLE: Fake Users JSON array generated by ChatGPT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": 1, | |
"first_name": "Alice", | |
"last_name": "Smith", | |
"email": "[email protected]", | |
"age": 28, | |
"last_login_date": "2023-08-09T10:30:00", | |
"friends_user_ids": [2, 3, 5, 7], | |
"favorite_animal": "Dog", | |
"least_favorite_smell": "Durian", | |
"hobbies": ["Painting", "Hiking"], | |
"birthplace": "Cityville", | |
"favorite_color": "Purple", | |
"unread_messages": 3, | |
"has_pets": true, | |
"shoe_size": 7.5, | |
"coffee_preference": "Black", | |
"dream_destination": "Bora Bora", | |
"music_genre": "Indie Rock", | |
"is_vegan": false, | |
"fitness_level": "Intermediate", | |
"lucky_number": 17 | |
}, | |
{ | |
"id": 2, | |
"first_name": "Bob", | |
"last_name": "Johnson", | |
"email": "[email protected]", | |
"age": 35, | |
"last_login_date": "2023-08-08T15:20:00", | |
"friends_user_ids": [1, 4, 8], | |
"favorite_animal": "Cat", | |
"least_favorite_smell": "Sulfur", | |
"hobbies": ["Reading", "Gardening"], | |
"birthplace": "Villageville", | |
"favorite_color": "Blue", | |
"unread_messages": 0, | |
"has_pets": true, | |
"shoe_size": 9.0, | |
"coffee_preference": "Latte", | |
"dream_destination": "Paris", | |
"music_genre": "Classical", | |
"is_vegan": true, | |
"fitness_level": "Beginner", | |
"lucky_number": 5 | |
}, | |
{ | |
"id": 3, | |
"first_name": "Charlie", | |
"last_name": "Brown", | |
"email": "[email protected]", | |
"age": 22, | |
"last_login_date": "2023-08-09T08:45:00", | |
"friends_user_ids": [1, 5, 9], | |
"favorite_animal": "Elephant", | |
"least_favorite_smell": "Fish", | |
"hobbies": ["Cooking", "Playing Guitar"], | |
"birthplace": "Townsville", | |
"favorite_color": "Green", | |
"unread_messages": 10, | |
"has_pets": false, | |
"shoe_size": 8.5, | |
"coffee_preference": "Cappuccino", | |
"dream_destination": "Tokyo", | |
"music_genre": "Pop", | |
"is_vegan": false, | |
"fitness_level": "Advanced", | |
"lucky_number": 12 | |
}, | |
{ | |
"id": 4, | |
"first_name": "David", | |
"last_name": "Lee", | |
"email": "[email protected]", | |
"age": 31, | |
"last_login_date": "2023-08-07T18:10:00", | |
"friends_user_ids": [2, 8, 10], | |
"favorite_animal": "Lion", | |
"least_favorite_smell": "Garlic", | |
"hobbies": ["Soccer", "Photography"], | |
"birthplace": "Countryville", | |
"favorite_color": "Yellow", | |
"unread_messages": 5, | |
"has_pets": true, | |
"shoe_size": 10.5, | |
"coffee_preference": "Espresso", | |
"dream_destination": "New York", | |
"music_genre": "Rock", | |
"is_vegan": false, | |
"fitness_level": "Intermediate", | |
"lucky_number": 9 | |
}, | |
{ | |
"id": 5, | |
"first_name": "Eve", | |
"last_name": "Williams", | |
"email": "[email protected]", | |
"age": 27, | |
"last_login_date": "2023-08-09T12:05:00", | |
"friends_user_ids": [1, 3, 9, 11], | |
"favorite_animal": "Dolphin", | |
"least_favorite_smell": "Onion", | |
"hobbies": ["Yoga", "Traveling"], | |
"birthplace": "Seaville", | |
"favorite_color": "Turquoise", | |
"unread_messages": 8, | |
"has_pets": true, | |
"shoe_size": 8.0, | |
"coffee_preference": "Mocha", | |
"dream_destination": "Maldives", | |
"music_genre": "Electronic", | |
"is_vegan": true, | |
"fitness_level": "Advanced", | |
"lucky_number": 3 | |
}, | |
{ | |
"id": 6, | |
"first_name": "Frank", | |
"last_name": "Davis", | |
"email": "[email protected]", | |
"age": 45, | |
"last_login_date": "2023-08-08T09:40:00", | |
"friends_user_ids": [12, 13, 14], | |
"favorite_animal": "Giraffe", | |
"least_favorite_smell": "Vinegar", | |
"hobbies": ["Golf", "Painting"], | |
"birthplace": "Mountainville", | |
"favorite_color": "Orange", | |
"unread_messages": 2, | |
"has_pets": false, | |
"shoe_size": 11.0, | |
"coffee_preference": "Americano", | |
"dream_destination": "Switzerland", | |
"music_genre": "Jazz", | |
"is_vegan": false, | |
"fitness_level": "Beginner", | |
"lucky_number": 8 | |
}, | |
{ | |
"id": 7, | |
"first_name": "Grace", | |
"last_name": "Martinez", | |
"email": "[email protected]", | |
"age": 29, | |
"last_login_date": "2023-08-09T16:15:00", | |
"friends_user_ids": [1, 15, 16], | |
"favorite_animal": "Penguin", | |
"least_favorite_smell": "Cabbage", | |
"hobbies": ["Singing", "Cooking"], | |
"birthplace": "Snowville", | |
"favorite_color": "White", | |
"unread_messages": 1, | |
"has_pets": true, | |
"shoe_size": 6.5, | |
"coffee_preference": "Latte", | |
"dream_destination": "Hawaii", | |
"music_genre": "Reggae", | |
"is_vegan": true, | |
"fitness_level": "Intermediate", | |
"lucky_number": 13 | |
}, | |
{ | |
"id": 8, | |
"first_name": "Henry", | |
"last_name": "Taylor", | |
"email": "[email protected]", | |
"age": 32, | |
"last_login_date": "2023-08-08T13:30:00", | |
"friends_user_ids": [2, 4, 9, 17], | |
"favorite_animal": "Tiger", | |
"least_favorite_smell": "Mold", | |
"hobbies": ["Running", "Chess"], | |
"birthplace": "Desertville", | |
"favorite_color": "Red", | |
"unread_messages": 12, | |
"has_pets": true, | |
"shoe_size": 10.0, | |
"coffee_preference": "Cappuccino", | |
"dream_destination": "Australia", | |
"music_genre": "Country", | |
"is_vegan": false, | |
"fitness_level": "Advanced", | |
"lucky_number": 21 | |
}, | |
{ | |
"id": 9, | |
"first_name": "Ivy", | |
"last_name": "Anderson", | |
"email": "[email protected]", | |
"age": 24, | |
"last_login_date": "2023-08-09T07:55:00", | |
"friends_user_ids": [3, 5, 8, 18], | |
"favorite_animal": "Kangaroo", | |
"least_favorite_smell": "Smoked Fish", | |
"hobbies": ["Dancing", "Biking"], | |
"birthplace": "Jungleville", | |
"favorite_color": "Yellow", | |
"unread_messages": 6, | |
"has_pets": true, | |
"shoe_size": 8.0, | |
"coffee_preference": "Espresso", | |
"dream_destination": "Brazil", | |
"music_genre": "Hip Hop", | |
"is_vegan": true, | |
"fitness_level": "Beginner", | |
"lucky_number": 6 | |
}, | |
{ | |
"id": 10, | |
"first_name": "Jack", | |
"last_name": "Moore", | |
"email": "[email protected]", | |
"age": 30, | |
"last_login_date": "2023-08-07T21:20:00", | |
"friends_user_ids": [4, 7, 9, 19], | |
"favorite_animal": "Horse", | |
"least_favorite_smell": "Burnt Rubber", | |
"hobbies": ["Photography", "Travelling"], | |
"birthplace": "Farmville", | |
"favorite_color": "Brown", | |
"unread_messages": 9, | |
"has_pets": true, | |
"shoe_size": 10.5, | |
"coffee_preference": "Black", | |
"dream_destination": "New Zealand", | |
"music_genre": "Alternative", | |
"is_vegan": false, | |
"fitness_level": "Intermediate", | |
"lucky_number": 11 | |
}, | |
{ | |
"id": 11, | |
"first_name": "Karen", | |
"last_name": "Jackson", | |
"email": "[email protected]", | |
"age": 26, | |
"last_login_date": "2023-08-09T14:50:00", | |
"friends_user_ids": [5, 10, 20], | |
"favorite_animal": "Panda", | |
"least_favorite_smell": "Rotting Fruit", | |
"hobbies": ["Yoga", "Reading"], | |
"birthplace": "Cityville", | |
"favorite_color": "Pink", | |
"unread_messages": 4, | |
"has_pets": false, | |
"shoe_size": 7.0, | |
"coffee_preference": "Mocha", | |
"dream_destination": "Thailand", | |
"music_genre": "R&B", | |
"is_vegan": true, | |
"fitness_level": "Advanced", | |
"lucky_number": 15 | |
}, | |
{ | |
"id": 12, | |
"first_name": "Liam", | |
"last_name": "Garcia", | |
"email": "[email protected]", | |
"age": 21, | |
"last_login_date": "2023-08-08T08:00:00", | |
"friends_user_ids": [6, 13, 15], | |
"favorite_animal": "Sloth", | |
"least_favorite_smell": "Dirty Socks", | |
"hobbies": ["Gaming", "Cooking"], | |
"birthplace": "Villageville", | |
"favorite_color": "Black", | |
"unread_messages": 7, | |
"has_pets": true, | |
"shoe_size": 9.5, | |
"coffee_preference": "Latte", | |
"dream_destination": "Greece", | |
"music_genre": "Metal", | |
"is_vegan": false, | |
"fitness_level": "Beginner", | |
"lucky_number": 14 | |
}, | |
{ | |
"id": 13, | |
"first_name": "Mia", | |
"last_name": "Rodriguez", | |
"email": "[email protected]", | |
"age": 33, | |
"last_login_date": "2023-08-09T11:40:00", | |
"friends_user_ids": [6, 12, 14, 16], | |
"favorite_animal": "Owl", | |
"least_favorite_smell": "Stale Beer", | |
"hobbies": ["Singing", "Painting"], | |
"birthplace": "Countryville", | |
"favorite_color": "Blue", | |
"unread_messages": 5, | |
"has_pets": true, | |
"shoe_size": 8.5, | |
"coffee_preference": "Cappuccino", | |
"dream_destination": "Italy", | |
"music_genre": "Pop", | |
"is_vegan": true, | |
"fitness_level": "Intermediate", | |
"lucky_number": 8 | |
}, | |
{ | |
"id": 14, | |
"first_name": "Noah", | |
"last_name": "Martinez", | |
"email": "[email protected]", | |
"age": 40, | |
"last_login_date": "2023-08-08T17:55:00", | |
"friends_user_ids": [6, 13, 15], | |
"favorite_animal": "Wolf", | |
"least_favorite_smell": "Mothballs", | |
"hobbies": ["Running", "Cooking"], | |
"birthplace": "Cityville", | |
"favorite_color": "Green", | |
"unread_messages": 2, | |
"has_pets": false, | |
"shoe_size": 10.0, | |
"coffee_preference": "Espresso", | |
"dream_destination": "Australia", | |
"music_genre": "Rock", | |
"is_vegan": false, | |
"fitness_level": "Advanced", | |
"lucky_number": 19 | |
}, | |
{ | |
"id": 15, | |
"first_name": "Olivia", | |
"last_name": "Thompson", | |
"email": "[email protected]", | |
"age": 29, | |
"last_login_date": "2023-08-09T10:15:00", | |
"friends_user_ids": [7, 13, 14, 17], | |
"favorite_animal": "Duck", | |
"least_favorite_smell": "Wet Dog", | |
"hobbies": ["Soccer", "Gardening"], | |
"birthplace": "Townsville", | |
"favorite_color": "Red", | |
"unread_messages": 1, | |
"has_pets": true, | |
"shoe_size": 7.0, | |
"coffee_preference": "Black", | |
"dream_destination": "Japan", | |
"music_genre": "Electronic", | |
"is_vegan": true, | |
"fitness_level": "Intermediate", | |
"lucky_number": 10 | |
}, | |
{ | |
"id": 16, | |
"first_name": "Peter", | |
"last_name": "Wright", | |
"email": "[email protected]", | |
"age": 37, | |
"last_login_date": "2023-08-08T12:30:00", | |
"friends_user_ids": [6, 15, 18], | |
"favorite_animal": "Bear", | |
"least_favorite_smell": "Gasoline", | |
"hobbies": ["Reading", "Biking"], | |
"birthplace": "Seaville", | |
"favorite_color": "Blue", | |
"unread_messages": 0, | |
"has_pets": false, | |
"shoe_size": 9.5, | |
"coffee_preference": "Mocha", | |
"dream_destination": "New Zealand", | |
"music_genre": "Indie Rock", | |
"is_vegan": false, | |
"fitness_level": "Advanced", | |
"lucky_number": 7 | |
}, | |
{ | |
"id": 17, | |
"first_name": "Quinn", | |
"last_name": "Hernandez", | |
"email": "[email protected]", | |
"age": 25, | |
"last_login_date": "2023-08-09T09:05:00", | |
"friends_user_ids": [8, 16, 19], | |
"favorite_animal": "Fox", | |
"least_favorite_smell": "Wet Carpet", | |
"hobbies": ["Painting", "Singing"], | |
"birthplace": "Mountainville", | |
"favorite_color": "Orange", | |
"unread_messages": 4, | |
"has_pets": true, | |
"shoe_size": 8.0, | |
"coffee_preference": "Latte", | |
"dream_destination": "Greece", | |
"music_genre": "Pop", | |
"is_vegan": true, | |
"fitness_level": "Beginner", | |
"lucky_number": 22 | |
}, | |
{ | |
"id": 18, | |
"first_name": "Ryan", | |
"last_name": "Adams", | |
"email": "[email protected]", | |
"age": 32, | |
"last_login_date": "2023-08-08T14:25:00", | |
"friends_user_ids": [9, 15, 20], | |
"favorite_animal": "Gorilla", | |
"least_favorite_smell": "Burnt Popcorn", | |
"hobbies": ["Gaming", "Hiking"], | |
"birthplace": "Villageville", | |
"favorite_color": "Green", | |
"unread_messages": 6, | |
"has_pets": true, | |
"shoe_size": 10.5, | |
"coffee_preference": "Black", | |
"dream_destination": "Hawaii", | |
"music_genre": "Electronic", | |
"is_vegan": false, | |
"fitness_level": "Intermediate", | |
"lucky_number": 18 | |
}, | |
{ | |
"id": 19, | |
"first_name": "Sophia", | |
"last_name": "Gonzalez", | |
"email": "[email protected]", | |
"age": 28, | |
"last_login_date": "2023-08-09T13:00:00", | |
"friends_user_ids": [10, 17, 20], | |
"favorite_animal": "Llama", | |
"least_favorite_smell": "Public Restrooms", | |
"hobbies": ["Reading", "Dancing"], | |
"birthplace": "Cityville", | |
"favorite_color": "Purple", | |
"unread_messages": 2, | |
"has_pets": true, | |
"shoe_size": 8.0, | |
"coffee_preference": "Latte", | |
"dream_destination": "Paris", | |
"music_genre": "Pop", | |
"is_vegan": true, | |
"fitness_level": "Advanced", | |
"lucky_number": 4 | |
}, | |
{ | |
"id": 20, | |
"first_name": "Thomas", | |
"last_name": "Walker", | |
"email": "[email protected]", | |
"age": 30, | |
"last_login_date": "2023-08-08T19:45:00", | |
"friends_user_ids": [11, 19], | |
"favorite_animal": "Monkey", | |
"least_favorite_smell": "Sewage", | |
"hobbies": ["Cooking", "Traveling"], | |
"birthplace": "Townsville", | |
"favorite_color": "Blue", | |
"unread_messages": 7, | |
"has_pets": false, | |
"shoe_size": 9.5, | |
"coffee_preference": "Cappuccino", | |
"dream_destination": "Maldives", | |
"music_genre": "Rock", | |
"is_vegan": false, | |
"fitness_level": "Intermediate", | |
"lucky_number": 16 | |
} | |
] |
Create a user profile UI using Compose's @composable functions to display information about a selected user.
Your task is to design and implement a user interface that showcases some of the user's details, you don't need to feature them all.
To fulfill the requirements, you need to use at least three different @composable functions, utilize a ViewModel
to manage data for this view, ensure data is passed from the top-level down to the components, and make use of the provided user data.
A sample response to the prompt:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
data class UserViewModel(
val first_name: String,
val last_name: String,
val age: Int,
val email: String,
val favorite_animal: String,
val hobbies: List<String>
)
class UserProfileViewModel(
private val userRepository: List<User> = User.parse(data)
) : ViewModel() {
fun getUserViewModel(userId: Int): UserViewModel? {
val user = userRepository.find { it.id == userId }
return user?.let {
UserViewModel(
first_name = it.first_name,
last_name = it.last_name,
age = it.age,
email = it.email,
favorite_animal = it.favorite_animal,
hobbies = it.hobbies
)
}
}
}
@Composable
fun UserProfileScreen(userId: Int) {
val viewModel: UserProfileViewModel = viewModel()
val userViewModel = remember {
mutableStateOf(viewModel.getUserViewModel(userId))
}
userViewModel.value?.let { user ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Text(
text = "User Profile",
style = MaterialTheme.typography.headlineSmall
)
Spacer(modifier = Modifier.height(16.dp))
UserDetailCard(user)
Spacer(modifier = Modifier.height(16.dp))
UserHobbies(user.hobbies)
}
} ?: run {
Text(
text = "User not found",
style = MaterialTheme.typography.headlineMedium
)
}
}
@Composable
fun UserDetailCard(user: UserViewModel) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "${user.first_name} ${user.last_name}",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Age: ${user.age}",
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Email: ${user.email}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Favorite Animal: ${user.favorite_animal}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
// Other user details...
}
}
}
@Composable
fun UserHobbies(hobbies: List<String>) {
Column(
modifier = Modifier.semantics { contentDescription = "User's hobbies" }
) {
Text(
text = "Hobbies",
style = MaterialTheme.typography.labelSmall
)
Spacer(modifier = Modifier.height(8.dp))
Column {
hobbies.forEach { hobby ->
Text(
text = "- $hobby",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
@Composable
fun UserProfileApp() {
val userId = (1..20).random()
UserProfileScreen(userId = userId) // Pass the desired user ID
}
@Preview(showBackground = true)
@Composable
fun UserProfileAppPreview() {
UserProfileApp()
}
Which ends up looking like:
MainAcitivy.kt
package com.blakebarrett.myapplication
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.blakebarrett.myapplication.ui.theme.DIALTheme
import kotlinx.coroutines.launch
/**
* This app is a simple phone dialer example written in Jetpack Compose.
* There are three main screens:
* 1. Dialer screen
* 2. Call history screen
* 3. Contact list screen
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DIALTheme {
// DialerApp()
UserProfileApp()
}
}
}
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
DIALTheme {
// DialerApp()
UserProfileApp()
}
}
/**
* The main app composable.
* This will either render a Scaffold on phones or a SideNav on tablets.
*/
@Composable
fun DialerApp() {
var isDialing = rememberSaveable {
mutableStateOf(false)
}
var dialEntry = rememberSaveable {
mutableStateOf("867-5309")
}
DialerAppPhone(
modifier = Modifier
.background(MaterialTheme.colorScheme.primaryContainer)
) { modifier ->
Box(modifier = modifier) {
DialerScreen(
currentEntry = dialEntry.value,
stateChange = { updatedEntry ->
dialEntry.value = updatedEntry
},
dial = { entry ->
isDialing.value = true
}
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DialerAppPhone(
modifier: Modifier = Modifier,
content: @Composable (modifier: Modifier) -> Unit = {}
) {
val coroutineScope = rememberCoroutineScope()
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = SheetState(
initialValue = SheetValue.Hidden,
skipPartiallyExpanded = true
)
)
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
Row(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.wrapContentWidth()
) {
Button(
onClick = {},
) {
Text(text = "Dial Pad")
}
Button(
modifier = Modifier.padding(horizontal = 8.dp),
onClick = {},
) {
Text(text = "Call History")
}
Button(
onClick = {},
) {
Text(text = "Contacts")
}
}
},
sheetPeekHeight = 32.dp,
sheetShape = MaterialTheme.shapes.extraLarge,
sheetShadowElevation = 10.dp,
sheetTonalElevation = 10.dp,
containerColor = MaterialTheme.colorScheme.primaryContainer,
modifier = Modifier
.clickable {
coroutineScope.launch {
if (bottomSheetScaffoldState.bottomSheetState.isVisible) {
bottomSheetScaffoldState.bottomSheetState.hide()
} else {
bottomSheetScaffoldState.bottomSheetState.expand()
}
}
}
.background(MaterialTheme.colorScheme.primaryContainer)
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
) {
content(
modifier = modifier
)
}
}
}
/*
@Composable
private fun SetupBottomSheet() {
val coroutineScope = rememberCoroutineScope()
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = SheetState(
initialValue = SheetValue.Hidden,
skipPartiallyExpanded = true
)
)
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetContent = {
Row (
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.wrapContentWidth()
) {
Button(
onClick = {},
) {
Text(text = "Dial Pad")
}
Button(
modifier = Modifier.padding(horizontal = 8.dp),
onClick = {},
) {
Text(text = "Call History")
}
Button(
onClick = {},
) {
Text(text = "Contacts")
}
}
},
sheetPeekHeight = 0.dp,
sheetShape = MaterialTheme.shapes.extraLarge,
sheetShadowElevation = 10.dp,
sheetTonalElevation = 10.dp,
) {
Column {
// Main Content
Button(onClick = {
coroutineScope.launch {
if (bottomSheetScaffoldState.bottomSheetState.isVisible) {
bottomSheetScaffoldState.bottomSheetState.hide()
} else {
bottomSheetScaffoldState.bottomSheetState.expand()
}
}
}) {
Text(text = "Open Bottom Sheet")
}
}
}
}
*/
/**
* Composable for the Dialer screen.
* This has two main parts:
* 1. The dialer pad
* 2. The current number that was entered.
* - This is a text field that is updated when the user presses a number on the dialer pad.
* - This is also updated when the user presses the backspace button.
* - State is remembered when the user navigates away from the screen.
* - State is remembered through recompositions (e.g. screen rotation).
*/
@Composable
fun DialerScreen(
currentEntry: String,
stateChange: (currentEntry: String) -> Unit,
dial: (entry: String) -> Unit,
modifier: Modifier = Modifier
) {
Column (
modifier = modifier
) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Text(
text = currentEntry,
modifier = Modifier
.padding(8.dp),
fontSize = MaterialTheme.typography.headlineLarge.fontSize,
maxLines = 1
)
}
DialerPad(
currentEntry = currentEntry,
stateChange = stateChange,
)
Button(
modifier = Modifier
.padding(8.dp)
.fillMaxSize(), // take up the rest of the space
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
),
onClick = {
dial(currentEntry)
}) {
Text(
text = "Call",
fontSize = MaterialTheme.typography.headlineLarge.fontSize,
fontFamily = MaterialTheme.typography.headlineMedium.fontFamily,
fontWeight = MaterialTheme.typography.headlineMedium.fontWeight,
fontStyle = MaterialTheme.typography.headlineMedium.fontStyle,
color = MaterialTheme.colorScheme.onPrimary,
)
}
}
}
/**
* Composable that returns a grid of buttons for the dialer pad.
* This is a 4x3 grid of buttons with the numbers 1-9, 0, and backspace.
* The backspace button is a special case because it is a square button that spans two columns.
* The backspace button is also disabled when there is no current entry.
*/
@Composable
fun DialerPad(
currentEntry: String,
stateChange: (newState: String) -> Unit,
modifier: Modifier = Modifier
) {
LazyVerticalGrid(
modifier = modifier,
columns = GridCells.Fixed(3),
) {
for (i in 1..9) {
item {
DialerButton(
text = i.toString(),
onClick = {
stateChange(currentEntry + i.toString())
}
)
}
}
item {
DialerButton(
text = "*",
onClick = {
stateChange("$currentEntry * ")
}
)
}
item {
DialerButton(
text = "0",
onClick = {
stateChange(currentEntry + "0")
}
)
}
item {
DialerButton(
text = "del",
onClick = {
if (currentEntry.isNotEmpty()) {
stateChange(currentEntry.dropLast(1))
}
}
)
}
}
}
@Composable
fun DialerButton(
text: String,
onClick: () -> Unit,
) {
Box(
modifier = Modifier
.aspectRatio(1f)
.fillMaxSize(),
) {
Button(
onClick = onClick,
modifier = Modifier
.padding(8.dp)
.fillMaxSize(),
) {
Text(
text = text
)
}
}
}
UserProfile.kt
package com.blakebarrett.myapplication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.blakebarrett.myapplication.ui.theme.DIALTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Preview(showBackground = true)
@Composable
fun UserProfileScreenPreview() {
DIALTheme {
InitAppWithNavigation()
}
}
@Composable
fun UserProfileApp() {
DIALTheme {
InitAppWithNavigation()
}
}
object Routes {
const val FRIENDS_LIST = "friendsList"
private const val PROFILE = "profile"
fun profile(userId: Int) = "$PROFILE/$userId"
}
@Composable
fun InitAppWithNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = Routes.FRIENDS_LIST
) {
composable(
"profile/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.IntType })
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId")
userId?.let {
Scaffold(
bottomBar = {
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.Magenta),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
) {
Text(
text = "All Users",
modifier = Modifier
.weight(1f)
.clickable {
navController.navigate(Routes.FRIENDS_LIST)
}
.padding(16.dp),
fontStyle = MaterialTheme.typography.bodyMedium.fontStyle
)
Text(
text = "Back",
modifier = Modifier
.weight(1f)
.clickable {
navController.popBackStack()
}
.padding(16.dp),
fontStyle = MaterialTheme.typography.bodyMedium.fontStyle
)
}
}
) { padding ->
UserProfileScreen(
modifier = Modifier.padding(padding),
userId = userId,
navigateToUserId = { friendId ->
navController.navigate(Routes.profile(friendId))
}
)
}
}
}
composable(Routes.FRIENDS_LIST) {
LazyColumn (
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
val allUsers = User.parse(data)
items(allUsers.count()) {index ->
val user = allUsers[index]
Box(
modifier = Modifier
.fillMaxWidth()
.background(Color.LightGray, MaterialTheme.shapes.medium)
.clickable {
navController.navigate(Routes.profile(user.id))
}
.padding(16.dp)
) {
Text("${user.first_name} ${user.last_name}")
}
}
}
}
}
}
@Composable
fun UserProfileScreen(modifier: Modifier = Modifier,
userId: Int,
navigateToUserId: (userId: Int) -> Unit = { _ -> }) {
val model = viewModel<UserViewModelLocator>()
model.updateUiStateWithUserViewModel(userId)
model.uiState.collectAsStateWithLifecycle().let { user ->
Column(
modifier = modifier
) {
Text(
text = "User Profile",
style = MaterialTheme.typography.headlineSmall
)
Spacer(modifier = Modifier.height(16.dp))
user.value?.let {
UserDetailCard(it, navigateToUserId)
}
Spacer(modifier = Modifier.height(16.dp))
}
} ?: run {
Text(
text = "User not found",
style = MaterialTheme.typography.headlineMedium
)
}
}
@Composable
fun UserDetailCard(
user: UserViewModelUiState,
friendClick: (Int) -> Unit = { _ -> }
) {
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(
defaultElevation = 10.dp
)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "${user.first_name} ${user.last_name}",
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Age: ${user.age}",
style = MaterialTheme.typography.bodyMedium
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Email: ${user.email}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Favorite Animal: ${user.favorite_animal}",
style = MaterialTheme.typography.bodySmall
)
Spacer(modifier = Modifier.height(8.dp))
UserFriendsList(
user.friends_user_ids,
onFriendClicked = friendClick
)
Spacer(modifier = Modifier.height(8.dp))
UserHobbies(user.hobbies)
}
}
}
@Composable
fun UserHobbies(hobbies: List<String>) {
Column {
Text(
text = "Hobbies",
style = MaterialTheme.typography.labelSmall
)
Spacer(modifier = Modifier.height(8.dp))
Column {
hobbies.forEach { hobby ->
Text(
text = "- $hobby",
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
@Composable
fun UserFriendsList(
friends_user_ids: List<Int>,
onFriendClicked: (Int) -> Unit = {}
) {
Column {
Text(
text = "Friends",
style = MaterialTheme.typography.labelSmall
)
Spacer(modifier = Modifier.height(8.dp))
Column {
friends_user_ids.forEach { friendId ->
viewModel<UserViewModelLocator>()
.getUserById(friendId)?.let { user ->
Text(
text = "- ${user.first_name} ${user.last_name}",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.clickable {
onFriendClicked(friendId)
}
)
}
}
}
}
}
data class UserViewModelUiState(
val first_name: String,
val last_name: String,
val age: Int,
val email: String,
val favorite_animal: String,
val hobbies: List<String>,
val friends_user_ids: List<Int>
)
class UserViewModelLocator : ViewModel() {
private val _allUsers: List<User> = User.parse(data)
private val _uiState = MutableStateFlow<UserViewModelUiState?>(null)
val uiState: StateFlow<UserViewModelUiState?> = _uiState.asStateFlow()
fun getUserById(userId: Int): UserViewModelUiState? {
return _allUsers.find { it.id == userId }?.let { user ->
UserViewModelUiState(
first_name = user.first_name,
last_name = user.last_name,
age = user.age,
email = user.email,
favorite_animal = user.favorite_animal,
hobbies = user.hobbies,
friends_user_ids = user.friends_user_ids
)
}
}
fun updateUiStateWithUserViewModel(userId: Int) {
getUserById(userId)?.let { user ->
_uiState.update {
it?.copy(
first_name = user.first_name,
last_name = user.last_name,
age = user.age,
email = user.email,
favorite_animal = user.favorite_animal,
hobbies = user.hobbies,
friends_user_ids = user.friends_user_ids
) ?: run {
UserViewModelUiState(
first_name = user.first_name,
last_name = user.last_name,
age = user.age,
email = user.email,
favorite_animal = user.favorite_animal,
hobbies = user.hobbies,
friends_user_ids = user.friends_user_ids
)
}
}
}
}
}
@Serializable
data class User(
val id: Int,
val first_name: String,
val last_name: String,
val email: String,
val age: Int,
val last_login_date: String,
val friends_user_ids: List<Int>,
val favorite_animal: String,
val least_favorite_smell: String,
val hobbies: List<String>,
val birthplace: String,
val favorite_color: String,
val unread_messages: Int,
val has_pets: Boolean,
val shoe_size: Double,
val coffee_preference: String,
val dream_destination: String,
val music_genre: String,
val is_vegan: Boolean,
val fitness_level: String,
val lucky_number: Int
) {
companion object {
fun parse(source: String): List<User> {
return Json.decodeFromString(source)
}
}
}
const val data = """
[
{
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"email": "[email protected]",
"age": 28,
"last_login_date": "2023-08-09T10:30:00",
"friends_user_ids": [2, 3, 5, 7],
"favorite_animal": "Dog",
"least_favorite_smell": "Durian",
"hobbies": ["Painting", "Hiking"],
"birthplace": "Cityville",
"favorite_color": "Purple",
"unread_messages": 3,
"has_pets": true,
"shoe_size": 7.5,
"coffee_preference": "Black",
"dream_destination": "Bora Bora",
"music_genre": "Indie Rock",
"is_vegan": false,
"fitness_level": "Intermediate",
"lucky_number": 17
},
{
"id": 2,
"first_name": "Bob",
"last_name": "Johnson",
"email": "[email protected]",
"age": 35,
"last_login_date": "2023-08-08T15:20:00",
"friends_user_ids": [1, 4, 8],
"favorite_animal": "Cat",
"least_favorite_smell": "Sulfur",
"hobbies": ["Reading", "Gardening"],
"birthplace": "Villageville",
"favorite_color": "Blue",
"unread_messages": 0,
"has_pets": true,
"shoe_size": 9.0,
"coffee_preference": "Latte",
"dream_destination": "Paris",
"music_genre": "Classical",
"is_vegan": true,
"fitness_level": "Beginner",
"lucky_number": 5
},
{
"id": 3,
"first_name": "Charlie",
"last_name": "Brown",
"email": "[email protected]",
"age": 22,
"last_login_date": "2023-08-09T08:45:00",
"friends_user_ids": [1, 5, 9],
"favorite_animal": "Elephant",
"least_favorite_smell": "Fish",
"hobbies": ["Cooking", "Playing Guitar"],
"birthplace": "Townsville",
"favorite_color": "Green",
"unread_messages": 10,
"has_pets": false,
"shoe_size": 8.5,
"coffee_preference": "Cappuccino",
"dream_destination": "Tokyo",
"music_genre": "Pop",
"is_vegan": false,
"fitness_level": "Advanced",
"lucky_number": 12
},
{
"id": 4,
"first_name": "David",
"last_name": "Lee",
"email": "[email protected]",
"age": 31,
"last_login_date": "2023-08-07T18:10:00",
"friends_user_ids": [2, 8, 10],
"favorite_animal": "Lion",
"least_favorite_smell": "Garlic",
"hobbies": ["Soccer", "Photography"],
"birthplace": "Countryville",
"favorite_color": "Yellow",
"unread_messages": 5,
"has_pets": true,
"shoe_size": 10.5,
"coffee_preference": "Espresso",
"dream_destination": "New York",
"music_genre": "Rock",
"is_vegan": false,
"fitness_level": "Intermediate",
"lucky_number": 9
},
{
"id": 5,
"first_name": "Eve",
"last_name": "Williams",
"email": "[email protected]",
"age": 27,
"last_login_date": "2023-08-09T12:05:00",
"friends_user_ids": [1, 3, 9, 11],
"favorite_animal": "Dolphin",
"least_favorite_smell": "Onion",
"hobbies": ["Yoga", "Traveling"],
"birthplace": "Seaville",
"favorite_color": "Turquoise",
"unread_messages": 8,
"has_pets": true,
"shoe_size": 8.0,
"coffee_preference": "Mocha",
"dream_destination": "Maldives",
"music_genre": "Electronic",
"is_vegan": true,
"fitness_level": "Advanced",
"lucky_number": 3
},
{
"id": 6,
"first_name": "Frank",
"last_name": "Davis",
"email": "[email protected]",
"age": 45,
"last_login_date": "2023-08-08T09:40:00",
"friends_user_ids": [12, 13, 14],
"favorite_animal": "Giraffe",
"least_favorite_smell": "Vinegar",
"hobbies": ["Golf", "Painting"],
"birthplace": "Mountainville",
"favorite_color": "Orange",
"unread_messages": 2,
"has_pets": false,
"shoe_size": 11.0,
"coffee_preference": "Americano",
"dream_destination": "Switzerland",
"music_genre": "Jazz",
"is_vegan": false,
"fitness_level": "Beginner",
"lucky_number": 8
},
{
"id": 7,
"first_name": "Grace",
"last_name": "Martinez",
"email": "[email protected]",
"age": 29,
"last_login_date": "2023-08-09T16:15:00",
"friends_user_ids": [1, 15, 16],
"favorite_animal": "Penguin",
"least_favorite_smell": "Cabbage",
"hobbies": ["Singing", "Cooking"],
"birthplace": "Snowville",
"favorite_color": "White",
"unread_messages": 1,
"has_pets": true,
"shoe_size": 6.5,
"coffee_preference": "Latte",
"dream_destination": "Hawaii",
"music_genre": "Reggae",
"is_vegan": true,
"fitness_level": "Intermediate",
"lucky_number": 13
},
{
"id": 8,
"first_name": "Henry",
"last_name": "Taylor",
"email": "[email protected]",
"age": 32,
"last_login_date": "2023-08-08T13:30:00",
"friends_user_ids": [2, 4, 9, 17],
"favorite_animal": "Tiger",
"least_favorite_smell": "Mold",
"hobbies": ["Running", "Chess"],
"birthplace": "Desertville",
"favorite_color": "Red",
"unread_messages": 12,
"has_pets": true,
"shoe_size": 10.0,
"coffee_preference": "Cappuccino",
"dream_destination": "Australia",
"music_genre": "Country",
"is_vegan": false,
"fitness_level": "Advanced",
"lucky_number": 21
},
{
"id": 9,
"first_name": "Ivy",
"last_name": "Anderson",
"email": "[email protected]",
"age": 24,
"last_login_date": "2023-08-09T07:55:00",
"friends_user_ids": [3, 5, 8, 18],
"favorite_animal": "Kangaroo",
"least_favorite_smell": "Smoked Fish",
"hobbies": ["Dancing", "Biking"],
"birthplace": "Jungleville",
"favorite_color": "Yellow",
"unread_messages": 6,
"has_pets": true,
"shoe_size": 8.0,
"coffee_preference": "Espresso",
"dream_destination": "Brazil",
"music_genre": "Hip Hop",
"is_vegan": true,
"fitness_level": "Beginner",
"lucky_number": 6
},
{
"id": 10,
"first_name": "Jack",
"last_name": "Moore",
"email": "[email protected]",
"age": 30,
"last_login_date": "2023-08-07T21:20:00",
"friends_user_ids": [4, 7, 9, 19],
"favorite_animal": "Horse",
"least_favorite_smell": "Burnt Rubber",
"hobbies": ["Photography", "Travelling"],
"birthplace": "Farmville",
"favorite_color": "Brown",
"unread_messages": 9,
"has_pets": true,
"shoe_size": 10.5,
"coffee_preference": "Black",
"dream_destination": "New Zealand",
"music_genre": "Alternative",
"is_vegan": false,
"fitness_level": "Intermediate",
"lucky_number": 11
},
{
"id": 11,
"first_name": "Karen",
"last_name": "Jackson",
"email": "[email protected]",
"age": 26,
"last_login_date": "2023-08-09T14:50:00",
"friends_user_ids": [5, 10, 20],
"favorite_animal": "Panda",
"least_favorite_smell": "Rotting Fruit",
"hobbies": ["Yoga", "Reading"],
"birthplace": "Cityville",
"favorite_color": "Pink",
"unread_messages": 4,
"has_pets": false,
"shoe_size": 7.0,
"coffee_preference": "Mocha",
"dream_destination": "Thailand",
"music_genre": "R&B",
"is_vegan": true,
"fitness_level": "Advanced",
"lucky_number": 15
},
{
"id": 12,
"first_name": "Liam",
"last_name": "Garcia",
"email": "[email protected]",
"age": 21,
"last_login_date": "2023-08-08T08:00:00",
"friends_user_ids": [6, 13, 15],
"favorite_animal": "Sloth",
"least_favorite_smell": "Dirty Socks",
"hobbies": ["Gaming", "Cooking"],
"birthplace": "Villageville",
"favorite_color": "Black",
"unread_messages": 7,
"has_pets": true,
"shoe_size": 9.5,
"coffee_preference": "Latte",
"dream_destination": "Greece",
"music_genre": "Metal",
"is_vegan": false,
"fitness_level": "Beginner",
"lucky_number": 14
},
{
"id": 13,
"first_name": "Mia",
"last_name": "Rodriguez",
"email": "[email protected]",
"age": 33,
"last_login_date": "2023-08-09T11:40:00",
"friends_user_ids": [6, 12, 14, 16],
"favorite_animal": "Owl",
"least_favorite_smell": "Stale Beer",
"hobbies": ["Singing", "Painting"],
"birthplace": "Countryville",
"favorite_color": "Blue",
"unread_messages": 5,
"has_pets": true,
"shoe_size": 8.5,
"coffee_preference": "Cappuccino",
"dream_destination": "Italy",
"music_genre": "Pop",
"is_vegan": true,
"fitness_level": "Intermediate",
"lucky_number": 8
},
{
"id": 14,
"first_name": "Noah",
"last_name": "Martinez",
"email": "[email protected]",
"age": 40,
"last_login_date": "2023-08-08T17:55:00",
"friends_user_ids": [6, 13, 15],
"favorite_animal": "Wolf",
"least_favorite_smell": "Mothballs",
"hobbies": ["Running", "Cooking"],
"birthplace": "Cityville",
"favorite_color": "Green",
"unread_messages": 2,
"has_pets": false,
"shoe_size": 10.0,
"coffee_preference": "Espresso",
"dream_destination": "Australia",
"music_genre": "Rock",
"is_vegan": false,
"fitness_level": "Advanced",
"lucky_number": 19
},
{
"id": 15,
"first_name": "Olivia",
"last_name": "Thompson",
"email": "[email protected]",
"age": 29,
"last_login_date": "2023-08-09T10:15:00",
"friends_user_ids": [7, 13, 14, 17],
"favorite_animal": "Duck",
"least_favorite_smell": "Wet Dog",
"hobbies": ["Soccer", "Gardening"],
"birthplace": "Townsville",
"favorite_color": "Red",
"unread_messages": 1,
"has_pets": true,
"shoe_size": 7.0,
"coffee_preference": "Black",
"dream_destination": "Japan",
"music_genre": "Electronic",
"is_vegan": true,
"fitness_level": "Intermediate",
"lucky_number": 10
},
{
"id": 16,
"first_name": "Peter",
"last_name": "Wright",
"email": "[email protected]",
"age": 37,
"last_login_date": "2023-08-08T12:30:00",
"friends_user_ids": [6, 15, 18],
"favorite_animal": "Bear",
"least_favorite_smell": "Gasoline",
"hobbies": ["Reading", "Biking"],
"birthplace": "Seaville",
"favorite_color": "Blue",
"unread_messages": 0,
"has_pets": false,
"shoe_size": 9.5,
"coffee_preference": "Mocha",
"dream_destination": "New Zealand",
"music_genre": "Indie Rock",
"is_vegan": false,
"fitness_level": "Advanced",
"lucky_number": 7
},
{
"id": 17,
"first_name": "Quinn",
"last_name": "Hernandez",
"email": "[email protected]",
"age": 25,
"last_login_date": "2023-08-09T09:05:00",
"friends_user_ids": [8, 16, 19],
"favorite_animal": "Fox",
"least_favorite_smell": "Wet Carpet",
"hobbies": ["Painting", "Singing"],
"birthplace": "Mountainville",
"favorite_color": "Orange",
"unread_messages": 4,
"has_pets": true,
"shoe_size": 8.0,
"coffee_preference": "Latte",
"dream_destination": "Greece",
"music_genre": "Pop",
"is_vegan": true,
"fitness_level": "Beginner",
"lucky_number": 22
},
{
"id": 18,
"first_name": "Ryan",
"last_name": "Adams",
"email": "[email protected]",
"age": 32,
"last_login_date": "2023-08-08T14:25:00",
"friends_user_ids": [9, 15, 20],
"favorite_animal": "Gorilla",
"least_favorite_smell": "Burnt Popcorn",
"hobbies": ["Gaming", "Hiking"],
"birthplace": "Villageville",
"favorite_color": "Green",
"unread_messages": 6,
"has_pets": true,
"shoe_size": 10.5,
"coffee_preference": "Black",
"dream_destination": "Hawaii",
"music_genre": "Electronic",
"is_vegan": false,
"fitness_level": "Intermediate",
"lucky_number": 18
},
{
"id": 19,
"first_name": "Sophia",
"last_name": "Gonzalez",
"email": "[email protected]",
"age": 28,
"last_login_date": "2023-08-09T13:00:00",
"friends_user_ids": [10, 17, 20],
"favorite_animal": "Llama",
"least_favorite_smell": "Public Restrooms",
"hobbies": ["Reading", "Dancing"],
"birthplace": "Cityville",
"favorite_color": "Purple",
"unread_messages": 2,
"has_pets": true,
"shoe_size": 8.0,
"coffee_preference": "Latte",
"dream_destination": "Paris",
"music_genre": "Pop",
"is_vegan": true,
"fitness_level": "Advanced",
"lucky_number": 4
},
{
"id": 20,
"first_name": "Thomas",
"last_name": "Walker",
"email": "[email protected]",
"age": 30,
"last_login_date": "2023-08-08T19:45:00",
"friends_user_ids": [11, 19],
"favorite_animal": "Monkey",
"least_favorite_smell": "Sewage",
"hobbies": ["Cooking", "Traveling"],
"birthplace": "Townsville",
"favorite_color": "Blue",
"unread_messages": 7,
"has_pets": false,
"shoe_size": 9.5,
"coffee_preference": "Cappuccino",
"dream_destination": "Maldives",
"music_genre": "Rock",
"is_vegan": false,
"fitness_level": "Intermediate",
"lucky_number": 16
}
]
"""
MyComposableUnitTest.kt
import androidx.compose.material3.Text
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import org.junit.Rule
import org.junit.Test
class MyComposableTest {
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testMyComposable() {
composeTestRule.setContent {
// Call your Composable function here
Text("Hello, World!")
}
// Use onNodeWithText or other assertions if needed
composeTestRule.onNodeWithText("Hello, World!").assertExists()
}
}
build.gradle
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("plugin.serialization") version "1.8.10"
}
android {
namespace = "com.blakebarrett.myapplication"
compileSdk = 34
defaultConfig {
applicationId = "com.blakebarrett.myapplication"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.3"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
val lifecycle_version = "2.6.1"
val compose_version = "1.5.0"
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.0")
// Kotlin serialization
implementation("org.jetbrains.kotlin:kotlin-serialization-compiler-plugin-embeddable:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.5.1")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
// ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
// ViewModel utilities for Compose
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
// LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
// Lifecycles only (without ViewModel or LiveData)
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
// Lifecycle utilities for Compose
implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version")
// Saved state module for ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
implementation("androidx.compose.runtime:runtime:$compose_version")
implementation("androidx.compose.runtime:runtime-livedata:$compose_version")
// Test rules and transitive dependencies:
testImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
// Needed for createAndroidComposeRule, but not createComposeRule:
testImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
// Test rules and transitive dependencies:
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
// Needed for createAndroidComposeRule, but not createComposeRule:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
// or Material Design 2
implementation("androidx.compose.material:material")
// or skip Material Design and build directly on top of foundational components
implementation("androidx.compose.foundation:foundation")
val nav_version = "2.7.1"
implementation("androidx.navigation:navigation-compose:$nav_version")
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample Kotlin parser, to get you unblocked.
You'll need to copy/paste the sample data above into
sampleDataJson: String
.