From 4b554fa465d26c045ec0cab525e42be823cb3857 Mon Sep 17 00:00:00 2001 From: Android dev Date: Wed, 24 May 2023 21:09:24 -0700 Subject: [PATCH 01/19] App code --- app/build.gradle | 10 +- .../java/com/example/inventory/ItemDaoTest.kt | 118 ------------------ .../example/inventory/data/AppContainer.kt | 2 +- .../inventory/data/InventoryDatabase.kt | 51 -------- .../java/com/example/inventory/data/Item.kt | 6 +- .../com/example/inventory/data/ItemDao.kt | 49 -------- .../example/inventory/data/ItemsRepository.kt | 29 +---- .../inventory/data/OfflineItemsRepository.kt | 14 +-- .../inventory/ui/AppViewModelProvider.kt | 10 +- .../example/inventory/ui/home/HomeScreen.kt | 10 +- .../inventory/ui/home/HomeViewModel.kt | 21 +--- .../inventory/ui/item/ItemDetailsScreen.kt | 77 ++++++------ .../inventory/ui/item/ItemDetailsViewModel.kt | 44 +------ .../inventory/ui/item/ItemEditScreen.kt | 16 +-- .../inventory/ui/item/ItemEditViewModel.kt | 23 ---- .../inventory/ui/item/ItemEntryScreen.kt | 14 +-- .../inventory/ui/item/ItemEntryViewModel.kt | 18 +-- .../com/example/inventory/ui/theme/Theme.kt | 2 +- 18 files changed, 60 insertions(+), 454 deletions(-) delete mode 100644 app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt delete mode 100644 app/src/main/java/com/example/inventory/data/InventoryDatabase.kt delete mode 100644 app/src/main/java/com/example/inventory/data/ItemDao.kt diff --git a/app/build.gradle b/app/build.gradle index 6fbeae44..6ebb2d29 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -67,22 +67,16 @@ dependencies { // Import the Compose BOM implementation platform('androidx.compose:compose-bom:2023.05.00') - implementation 'androidx.activity:activity-compose:1.7.1' + implementation 'androidx.activity:activity-compose:1.7.2' implementation 'androidx.compose.material3:material3' implementation "androidx.compose.ui:ui" implementation "androidx.compose.ui:ui-tooling" implementation "androidx.compose.ui:ui-tooling-preview" - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" implementation "androidx.navigation:navigation-compose:2.5.3" - //Room - implementation "androidx.room:room-runtime:$room_version" - implementation 'androidx.core:core-ktx:1.10.0' - ksp "androidx.room:room-compiler:$room_version" - implementation "androidx.room:room-ktx:$room_version" - // Testing androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" androidTestImplementation "androidx.test.ext:junit:1.1.5" diff --git a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt b/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt deleted file mode 100644 index da73558e..00000000 --- a/app/src/androidTest/java/com/example/inventory/ItemDaoTest.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.inventory - -import android.content.Context -import androidx.room.Room -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.example.inventory.data.InventoryDatabase -import com.example.inventory.data.Item -import com.example.inventory.data.ItemDao -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking -import org.junit.After -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import java.io.IOException - -@RunWith(AndroidJUnit4::class) -class ItemDaoTest { - - private lateinit var itemDao: ItemDao - private lateinit var inventoryDatabase: InventoryDatabase - private val item1 = Item(1, "Apples", 10.0, 20) - private val item2 = Item(2, "Bananas", 15.0, 97) - - @Before - fun createDb() { - val context: Context = ApplicationProvider.getApplicationContext() - // Using an in-memory database because the information stored here disappears when the - // process is killed. - inventoryDatabase = Room.inMemoryDatabaseBuilder(context, InventoryDatabase::class.java) - // Allowing main thread queries, just for testing. - .allowMainThreadQueries() - .build() - itemDao = inventoryDatabase.itemDao() - } - - @After - @Throws(IOException::class) - fun closeDb() { - inventoryDatabase.close() - } - - @Test - @Throws(Exception::class) - fun daoInsert_insertsItemIntoDB() = runBlocking { - addOneItemToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) - } - - @Test - @Throws(Exception::class) - fun daoGetAllItems_returnsAllItemsFromDB() = runBlocking { - addTwoItemsToDb() - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], item1) - assertEquals(allItems[1], item2) - } - - - @Test - @Throws(Exception::class) - fun daoGetItem_returnsItemFromDB() = runBlocking { - addOneItemToDb() - val item = itemDao.getItem(1) - assertEquals(item.first(), item1) - } - - @Test - @Throws(Exception::class) - fun daoDeleteItems_deletesAllItemsFromDB() = runBlocking { - addTwoItemsToDb() - itemDao.delete(item1) - itemDao.delete(item2) - val allItems = itemDao.getAllItems().first() - assertTrue(allItems.isEmpty()) - } - - @Test - @Throws(Exception::class) - fun daoUpdateItems_updatesItemsInDB() = runBlocking { - addTwoItemsToDb() - itemDao.update(Item(1, "Apples", 15.0, 25)) - itemDao.update(Item(2, "Bananas", 5.0, 50)) - - val allItems = itemDao.getAllItems().first() - assertEquals(allItems[0], Item(1, "Apples", 15.0, 25)) - assertEquals(allItems[1], Item(2, "Bananas", 5.0, 50)) - } - - private suspend fun addOneItemToDb() { - itemDao.insert(item1) - } - - private suspend fun addTwoItemsToDb() { - itemDao.insert(item1) - itemDao.insert(item2) - } -} diff --git a/app/src/main/java/com/example/inventory/data/AppContainer.kt b/app/src/main/java/com/example/inventory/data/AppContainer.kt index fc352312..0d4414c8 100644 --- a/app/src/main/java/com/example/inventory/data/AppContainer.kt +++ b/app/src/main/java/com/example/inventory/data/AppContainer.kt @@ -33,6 +33,6 @@ class AppDataContainer(private val context: Context) : AppContainer { * Implementation for [ItemsRepository] */ override val itemsRepository: ItemsRepository by lazy { - OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao()) + OfflineItemsRepository() } } diff --git a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt b/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt deleted file mode 100644 index d9977d9c..00000000 --- a/app/src/main/java/com/example/inventory/data/InventoryDatabase.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.inventory.data - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase - -/** - * Database class with a singleton Instance object. - */ -@Database(entities = [Item::class], version = 1, exportSchema = false) -abstract class InventoryDatabase : RoomDatabase() { - - abstract fun itemDao(): ItemDao - - companion object { - @Volatile - private var Instance: InventoryDatabase? = null - - fun getDatabase(context: Context): InventoryDatabase { - // if the Instance is not null, return it, otherwise create a new database instance. - return Instance ?: synchronized(this) { - Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database") - /** - * Setting this option in your app's database builder means that Room - * permanently deletes all data from the tables in your database when it - * attempts to perform a migration with no defined migration path. - */ - .fallbackToDestructiveMigration() - .build() - .also { Instance = it } - } - } - } -} diff --git a/app/src/main/java/com/example/inventory/data/Item.kt b/app/src/main/java/com/example/inventory/data/Item.kt index edad7cae..eeef6bf5 100644 --- a/app/src/main/java/com/example/inventory/data/Item.kt +++ b/app/src/main/java/com/example/inventory/data/Item.kt @@ -16,15 +16,11 @@ package com.example.inventory.data -import androidx.room.Entity -import androidx.room.PrimaryKey /** * Entity data class represents a single row in the database. */ -@Entity(tableName = "items") -data class Item( - @PrimaryKey(autoGenerate = true) +class Item( val id: Int = 0, val name: String, val price: Double, diff --git a/app/src/main/java/com/example/inventory/data/ItemDao.kt b/app/src/main/java/com/example/inventory/data/ItemDao.kt deleted file mode 100644 index 22b14c72..00000000 --- a/app/src/main/java/com/example/inventory/data/ItemDao.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.inventory.data - -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Update -import kotlinx.coroutines.flow.Flow - -/** - * Database access object to access the Inventory database - */ -@Dao -interface ItemDao { - - @Query("SELECT * from items ORDER BY name ASC") - fun getAllItems(): Flow> - - @Query("SELECT * from items WHERE id = :id") - fun getItem(id: Int): Flow - - // Specify the conflict strategy as IGNORE, when the user tries to add an - // existing Item into the database Room ignores the conflict. - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insert(item: Item) - - @Update - suspend fun update(item: Item) - - @Delete - suspend fun delete(item: Item) -} diff --git a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt b/app/src/main/java/com/example/inventory/data/ItemsRepository.kt index 57029541..b956ea6d 100644 --- a/app/src/main/java/com/example/inventory/data/ItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/ItemsRepository.kt @@ -16,34 +16,7 @@ package com.example.inventory.data -import kotlinx.coroutines.flow.Flow - /** * Repository that provides insert, update, delete, and retrieve of [Item] from a given data source. */ -interface ItemsRepository { - /** - * Retrieve all the items from the the given data source. - */ - fun getAllItemsStream(): Flow> - - /** - * Retrieve an item from the given data source that matches with the [id]. - */ - fun getItemStream(id: Int): Flow - - /** - * Insert item in the data source - */ - suspend fun insertItem(item: Item) - - /** - * Delete item from the data source - */ - suspend fun deleteItem(item: Item) - - /** - * Update item in the data source - */ - suspend fun updateItem(item: Item) -} +interface ItemsRepository diff --git a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt b/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt index ed4c03b7..d621a8f4 100644 --- a/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt +++ b/app/src/main/java/com/example/inventory/data/OfflineItemsRepository.kt @@ -16,16 +16,4 @@ package com.example.inventory.data -import kotlinx.coroutines.flow.Flow - -class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository { - override fun getAllItemsStream(): Flow> = itemDao.getAllItems() - - override fun getItemStream(id: Int): Flow = itemDao.getItem(id) - - override suspend fun insertItem(item: Item) = itemDao.insert(item) - - override suspend fun deleteItem(item: Item) = itemDao.delete(item) - - override suspend fun updateItem(item: Item) = itemDao.update(item) -} +class OfflineItemsRepository : ItemsRepository diff --git a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt index be50e541..8aa8b481 100644 --- a/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/inventory/ui/AppViewModelProvider.kt @@ -36,26 +36,24 @@ object AppViewModelProvider { // Initializer for ItemEditViewModel initializer { ItemEditViewModel( - this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + this.createSavedStateHandle() ) } // Initializer for ItemEntryViewModel initializer { - ItemEntryViewModel(inventoryApplication().container.itemsRepository) + ItemEntryViewModel() } // Initializer for ItemDetailsViewModel initializer { ItemDetailsViewModel( - this.createSavedStateHandle(), - inventoryApplication().container.itemsRepository + this.createSavedStateHandle() ) } // Initializer for HomeViewModel initializer { - HomeViewModel(inventoryApplication().container.itemsRepository) + HomeViewModel() } } } diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 7a5a3c9f..0b8f4fb4 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -38,8 +38,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll @@ -48,11 +46,9 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R import com.example.inventory.data.Item -import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.item.formatedPrice import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme @@ -70,10 +66,8 @@ object HomeDestination : NavigationDestination { fun HomeScreen( navigateToItemEntry: () -> Unit, navigateToItemUpdate: (Int) -> Unit, - modifier: Modifier = Modifier, - viewModel: HomeViewModel = viewModel(factory = AppViewModelProvider.Factory) + modifier: Modifier = Modifier ) { - val homeUiState by viewModel.homeUiState.collectAsState() val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() Scaffold( @@ -99,7 +93,7 @@ fun HomeScreen( }, ) { innerPadding -> HomeBody( - itemList = homeUiState.itemList, + itemList = listOf(), onItemClick = navigateToItemUpdate, modifier = modifier .padding(innerPadding) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt index 9370177d..b46e69fb 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt @@ -17,31 +17,12 @@ package com.example.inventory.ui.home import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.example.inventory.data.Item -import com.example.inventory.data.ItemsRepository -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn /** * View Model to retrieve all items in the Room database. */ -class HomeViewModel(itemsRepository: ItemsRepository) : ViewModel() { - - /** - * Holds home ui state. The list of items are retrieved from [ItemsRepository] and mapped to - * [HomeUiState] - */ - val homeUiState: StateFlow = - itemsRepository.getAllItemsStream().map { HomeUiState(it) } - .stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = HomeUiState() - ) - +class HomeViewModel : ViewModel() { companion object { private const val TIMEOUT_MILLIS = 5_000L } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt index 143eff2e..52724849 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt @@ -39,10 +39,8 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -50,14 +48,11 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.lifecycle.viewmodel.compose.viewModel import com.example.inventory.InventoryTopAppBar import com.example.inventory.R import com.example.inventory.data.Item -import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme -import kotlinx.coroutines.launch object ItemDetailsDestination : NavigationDestination { override val route = "item_details" @@ -70,11 +65,8 @@ object ItemDetailsDestination : NavigationDestination { fun ItemDetailsScreen( navigateToEditItem: (Int) -> Unit, navigateBack: () -> Unit, - modifier: Modifier = Modifier, - viewModel: ItemDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory) + modifier: Modifier = Modifier ) { - val uiState = viewModel.uiState.collectAsState() - val coroutineScope = rememberCoroutineScope() Scaffold(topBar = { InventoryTopAppBar( title = stringResource(ItemDetailsDestination.titleRes), @@ -83,7 +75,7 @@ fun ItemDetailsScreen( ) }, floatingActionButton = { FloatingActionButton( - onClick = { navigateToEditItem(uiState.value.itemDetails.id) }, + onClick = { navigateToEditItem(0) }, shape = MaterialTheme.shapes.medium, modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)) @@ -96,22 +88,13 @@ fun ItemDetailsScreen( }, modifier = modifier ) { innerPadding -> ItemDetailsBody( - itemDetailsUiState = uiState.value, - onSellItem = { viewModel.reduceQuantityByOne() }, - onDelete = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be deleted from the Database. This is because when config - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. - coroutineScope.launch { - viewModel.deleteItem() - navigateBack() - } - }, + itemDetailsUiState = ItemDetailsUiState(), + onSellItem = { }, + onDelete = { }, modifier = Modifier .padding(innerPadding) .verticalScroll(rememberScrollState()) - ) + ) } } @@ -127,6 +110,7 @@ private fun ItemDetailsBody( verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) ) { var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } + ItemDetails( item = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() ) @@ -134,7 +118,7 @@ private fun ItemDetailsBody( onClick = onSellItem, modifier = Modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - enabled = !itemDetailsUiState.outOfStock + enabled = true ) { Text(stringResource(R.string.sell)) } @@ -146,10 +130,11 @@ private fun ItemDetailsBody( Text(stringResource(R.string.delete)) } if (deleteConfirmationRequired) { - DeleteConfirmationDialog(onDeleteConfirm = { - deleteConfirmationRequired = false - onDelete() - }, + DeleteConfirmationDialog( + onDeleteConfirm = { + deleteConfirmationRequired = false + onDelete() + }, onDeleteCancel = { deleteConfirmationRequired = false }, modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_medium)) ) @@ -157,7 +142,6 @@ private fun ItemDetailsBody( } } - @Composable fun ItemDetails( item: Item, modifier: Modifier = Modifier @@ -177,20 +161,32 @@ fun ItemDetails( ItemDetailsRow( labelResID = R.string.item, itemDetail = item.name, - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource( + id = R.dimen + .padding_medium + ) + ) ) ItemDetailsRow( labelResID = R.string.quantity_in_stock, itemDetail = item.quantity.toString(), - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource( + id = R.dimen + .padding_medium + ) + ) ) ItemDetailsRow( labelResID = R.string.price, itemDetail = item.formatedPrice(), - modifier = Modifier.padding(horizontal = dimensionResource(id = R.dimen - .padding_medium)) + modifier = Modifier.padding( + horizontal = dimensionResource( + id = R.dimen + .padding_medium + ) + ) ) } @@ -232,8 +228,13 @@ private fun DeleteConfirmationDialog( @Composable fun ItemDetailsScreenPreview() { InventoryTheme { - ItemDetailsBody(ItemDetailsUiState( - outOfStock = true, itemDetails = ItemDetails(1, "Pen", "$100", "10") - ), onSellItem = {}, onDelete = {}) + ItemDetailsBody( + ItemDetailsUiState( + outOfStock = true, + itemDetails = ItemDetails(1, "Pen", "$100", "10") + ), + onSellItem = {}, + onDelete = {} + ) } } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt index c37b0f58..e92c5bd6 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsViewModel.kt @@ -18,59 +18,17 @@ package com.example.inventory.ui.item import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.example.inventory.data.ItemsRepository -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch /** * ViewModel to retrieve, update and delete an item from the [ItemsRepository]'s data source. */ class ItemDetailsViewModel( - savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository, + savedStateHandle: SavedStateHandle ) : ViewModel() { private val itemId: Int = checkNotNull(savedStateHandle[ItemDetailsDestination.itemIdArg]) - /** - * Holds the item details ui state. The data is retrieved from [ItemsRepository] and mapped to - * the UI state. - */ - val uiState: StateFlow = - itemsRepository.getItemStream(itemId) - .filterNotNull() - .map { - ItemDetailsUiState(outOfStock = it.quantity <= 0, itemDetails = it.toItemDetails()) - }.stateIn( - scope = viewModelScope, - started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS), - initialValue = ItemDetailsUiState() - ) - - /** - * Reduces the item quantity by one and update the [ItemsRepository]'s data source. - */ - fun reduceQuantityByOne() { - viewModelScope.launch { - val currentItem = uiState.value.itemDetails.toItem() - if (currentItem.quantity > 0) { - itemsRepository.updateItem(currentItem.copy(quantity = currentItem.quantity - 1)) - } - } - } - - /** - * Deletes the item from the [ItemsRepository]'s data source. - */ - suspend fun deleteItem() { - itemsRepository.deleteItem(uiState.value.itemDetails.toItem()) - } - companion object { private const val TIMEOUT_MILLIS = 5_000L } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt index f7a5ab42..45746a5c 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt @@ -19,7 +19,6 @@ package com.example.inventory.ui.item import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -29,7 +28,6 @@ import com.example.inventory.R import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme -import kotlinx.coroutines.launch object ItemEditDestination : NavigationDestination { override val route = "item_edit" @@ -45,7 +43,6 @@ fun ItemEditScreen( modifier: Modifier = Modifier, viewModel: ItemEditViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() Scaffold( topBar = { InventoryTopAppBar( @@ -58,17 +55,8 @@ fun ItemEditScreen( ) { innerPadding -> ItemEntryBody( itemUiState = viewModel.itemUiState, - onItemValueChange = viewModel::updateUiState, - onSaveClick = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be updated in the Database. This is because when config - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. - coroutineScope.launch { - viewModel.updateItem() - navigateBack() - } - }, + onItemValueChange = { }, + onSaveClick = { }, modifier = Modifier.padding(innerPadding) ) } diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt index bdd64918..fb50de77 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt @@ -21,18 +21,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.example.inventory.data.ItemsRepository -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch /** * ViewModel to retrieve and update an item from the [ItemsRepository]'s data source. */ class ItemEditViewModel( savedStateHandle: SavedStateHandle, - private val itemsRepository: ItemsRepository ) : ViewModel() { /** @@ -43,24 +38,6 @@ class ItemEditViewModel( private val itemId: Int = checkNotNull(savedStateHandle[ItemEditDestination.itemIdArg]) - init { - viewModelScope.launch { - itemUiState = itemsRepository.getItemStream(itemId) - .filterNotNull() - .first() - .toItemUiState(true) - } - } - - /** - * Update the item in the [ItemsRepository]'s data source - */ - suspend fun updateItem() { - if (validateInput(itemUiState.itemDetails)) { - itemsRepository.updateItem(itemUiState.itemDetails.toItem()) - } - } - /** * Updates the [itemUiState] with the value provided in the argument. This method also triggers * a validation for input values. diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt index f1b6bcb6..d7d5bf7f 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource @@ -42,7 +41,6 @@ import com.example.inventory.R import com.example.inventory.ui.AppViewModelProvider import com.example.inventory.ui.navigation.NavigationDestination import com.example.inventory.ui.theme.InventoryTheme -import kotlinx.coroutines.launch import java.util.Currency import java.util.Locale @@ -58,7 +56,6 @@ fun ItemEntryScreen( canNavigateBack: Boolean = true, viewModel: ItemEntryViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() Scaffold( topBar = { InventoryTopAppBar( @@ -71,16 +68,7 @@ fun ItemEntryScreen( ItemEntryBody( itemUiState = viewModel.itemUiState, onItemValueChange = viewModel::updateUiState, - onSaveClick = { - // Note: If the user rotates the screen very fast, the operation may get cancelled - // and the item may not be saved in the Database. This is because when config - // change occurs, the Activity will be recreated and the rememberCoroutineScope will - // be cancelled - since the scope is bound to composition. - coroutineScope.launch { - viewModel.saveItem() - navigateBack() - } - }, + onSaveClick = { }, modifier = Modifier .padding(innerPadding) .verticalScroll(rememberScrollState()) diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt index fbb01eec..acd502e3 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt @@ -21,13 +21,12 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.example.inventory.data.Item -import com.example.inventory.data.ItemsRepository import java.text.NumberFormat /** * View Model to validate and insert items in the Room database. */ -class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() { +class ItemEntryViewModel : ViewModel() { /** * Holds current item ui state @@ -44,15 +43,6 @@ class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewMod ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) } - /** - * Inserts an [Item] in the Room database - */ - suspend fun saveItem() { - if (validateInput()) { - itemsRepository.insertItem(itemUiState.itemDetails.toItem()) - } - } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() @@ -76,9 +66,9 @@ data class ItemDetails( ) /** - * Extension function to convert [ItemUiState] to [Item]. If the value of [ItemDetails.price] is + * Extension function to convert [ItemDetails] to [Item]. If the value of [ItemDetails.price] is * not a valid [Double], then the price will be set to 0.0. Similarly if the value of - * [ItemUiState] is not a valid [Int], then the quantity will be set to 0 + * [ItemDetails.quantity] is not a valid [Int], then the quantity will be set to 0 */ fun ItemDetails.toItem(): Item = Item( id = id, @@ -91,8 +81,6 @@ fun Item.formatedPrice(): String { return NumberFormat.getCurrencyInstance().format(price) } - - /** * Extension function to convert [Item] to [ItemUiState] */ diff --git a/app/src/main/java/com/example/inventory/ui/theme/Theme.kt b/app/src/main/java/com/example/inventory/ui/theme/Theme.kt index 64411b6a..7d9f69d3 100644 --- a/app/src/main/java/com/example/inventory/ui/theme/Theme.kt +++ b/app/src/main/java/com/example/inventory/ui/theme/Theme.kt @@ -116,7 +116,7 @@ fun InventoryTheme( if (!view.isInEditMode) { SideEffect { val window = (view.context as Activity).window - if(darkTheme) { + if (darkTheme) { window.statusBarColor = colorScheme.primary.toArgb() } else { window.statusBarColor = Color.Transparent.toArgb() From e4230ca44946b891ca44efbf89ffd5faa6c57f3b Mon Sep 17 00:00:00 2001 From: Android dev Date: Thu, 1 Jun 2023 17:20:20 -0700 Subject: [PATCH 02/19] Update ItemEditViewModel.kt --- .../com/example/inventory/ui/item/ItemEditViewModel.kt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt index fb50de77..abd424dc 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditViewModel.kt @@ -38,15 +38,6 @@ class ItemEditViewModel( private val itemId: Int = checkNotNull(savedStateHandle[ItemEditDestination.itemIdArg]) - /** - * Updates the [itemUiState] with the value provided in the argument. This method also triggers - * a validation for input values. - */ - fun updateUiState(itemDetails: ItemDetails) { - itemUiState = - ItemUiState(itemDetails = itemDetails, isEntryValid = validateInput(itemDetails)) - } - private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean { return with(uiState) { name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank() From 79d11efefe215e722963412ac02f19dba367ca98 Mon Sep 17 00:00:00 2001 From: Android dev Date: Thu, 1 Jun 2023 17:20:20 -0700 Subject: [PATCH 03/19] Update ItemEditViewModel.kt Feedback Update ItemDetailsScreen.kt --- .../example/inventory/ui/home/HomeScreen.kt | 5 +- .../inventory/ui/item/ItemDetailsScreen.kt | 67 +++++++++---------- .../inventory/ui/item/ItemEntryScreen.kt | 6 +- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 0b8f4fb4..2fb95501 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -95,7 +95,7 @@ fun HomeScreen( HomeBody( itemList = listOf(), onItemClick = navigateToItemUpdate, - modifier = modifier + modifier = Modifier .padding(innerPadding) .fillMaxSize() ) @@ -145,7 +145,8 @@ private fun InventoryItem( item: Item, modifier: Modifier = Modifier ) { Card( - modifier = modifier, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + modifier = modifier, + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column( modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)), diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt index 52724849..aed55423 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt @@ -67,25 +67,26 @@ fun ItemDetailsScreen( navigateBack: () -> Unit, modifier: Modifier = Modifier ) { - Scaffold(topBar = { - InventoryTopAppBar( - title = stringResource(ItemDetailsDestination.titleRes), - canNavigateBack = true, - navigateUp = navigateBack - ) - }, floatingActionButton = { - FloatingActionButton( - onClick = { navigateToEditItem(0) }, - shape = MaterialTheme.shapes.medium, - modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)) - - ) { - Icon( - imageVector = Icons.Default.Edit, - contentDescription = stringResource(R.string.edit_item_title), + Scaffold( + topBar = { + InventoryTopAppBar( + title = stringResource(ItemDetailsDestination.titleRes), + canNavigateBack = true, + navigateUp = navigateBack ) - } - }, modifier = modifier + }, floatingActionButton = { + FloatingActionButton( + onClick = { navigateToEditItem(0) }, + shape = MaterialTheme.shapes.medium, + modifier = Modifier.padding(dimensionResource(id = R.dimen.padding_large)) + + ) { + Icon( + imageVector = Icons.Default.Edit, + contentDescription = stringResource(R.string.edit_item_title), + ) + } + }, modifier = modifier ) { innerPadding -> ItemDetailsBody( itemDetailsUiState = ItemDetailsUiState(), @@ -112,7 +113,8 @@ private fun ItemDetailsBody( var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) } ItemDetails( - item = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth() + item = itemDetailsUiState.itemDetails.toItem(), + modifier = Modifier.fillMaxWidth() ) Button( onClick = onSellItem, @@ -147,7 +149,8 @@ fun ItemDetails( item: Item, modifier: Modifier = Modifier ) { Card( - modifier = modifier, colors = CardDefaults.cardColors( + modifier = modifier, + colors = CardDefaults.cardColors( containerColor = MaterialTheme.colorScheme.primaryContainer, contentColor = MaterialTheme.colorScheme.onPrimaryContainer ) @@ -156,40 +159,32 @@ fun ItemDetails( modifier = Modifier .fillMaxWidth() .padding(dimensionResource(id = R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_medium)) + verticalArrangement = Arrangement.spacedBy( + dimensionResource(id = R.dimen.padding_medium) + ) ) { ItemDetailsRow( labelResID = R.string.item, itemDetail = item.name, modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) + horizontal = dimensionResource(id = R.dimen.padding_medium) ) ) ItemDetailsRow( labelResID = R.string.quantity_in_stock, itemDetail = item.quantity.toString(), modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) + horizontal = dimensionResource(id = R.dimen.padding_medium) ) ) ItemDetailsRow( labelResID = R.string.price, itemDetail = item.formatedPrice(), modifier = Modifier.padding( - horizontal = dimensionResource( - id = R.dimen - .padding_medium - ) + horizontal = dimensionResource(id = R.dimen.padding_medium) ) ) } - } } @@ -206,7 +201,9 @@ private fun ItemDetailsRow( @Composable private fun DeleteConfirmationDialog( - onDeleteConfirm: () -> Unit, onDeleteCancel: () -> Unit, modifier: Modifier = Modifier + onDeleteConfirm: () -> Unit, + onDeleteCancel: () -> Unit, + modifier: Modifier = Modifier ) { AlertDialog(onDismissRequest = { /* Do nothing */ }, title = { Text(stringResource(R.string.attention)) }, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt index d7d5bf7f..68ba7076 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt @@ -85,9 +85,9 @@ fun ItemEntryBody( modifier: Modifier = Modifier ) { Column( - modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)), - verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)) - ) { + verticalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.padding_large)), + modifier = modifier.padding(dimensionResource(id = R.dimen.padding_medium)) + ) { ItemInputForm( itemDetails = itemUiState.itemDetails, onValueChange = onItemValueChange, From 9e6ebee4d0473fa604b958c197fadfea46e76857 Mon Sep 17 00:00:00 2001 From: Android dev Date: Tue, 6 Jun 2023 10:25:34 -0700 Subject: [PATCH 04/19] Update ItemDetailsScreen.kt --- .../java/com/example/inventory/ui/item/ItemDetailsScreen.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt index aed55423..0443a202 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt @@ -193,7 +193,7 @@ private fun ItemDetailsRow( @StringRes labelResID: Int, itemDetail: String, modifier: Modifier = Modifier ) { Row(modifier = modifier) { - Text(text = stringResource(labelResID)) + Text(stringResource(labelResID)) Spacer(modifier = Modifier.weight(1f)) Text(text = itemDetail, fontWeight = FontWeight.Bold) } @@ -211,12 +211,12 @@ private fun DeleteConfirmationDialog( modifier = modifier, dismissButton = { TextButton(onClick = onDeleteCancel) { - Text(text = stringResource(R.string.no)) + Text(stringResource(R.string.no)) } }, confirmButton = { TextButton(onClick = onDeleteConfirm) { - Text(text = stringResource(R.string.yes)) + Text(stringResource(R.string.yes)) } }) } From 4fd00772e32897fd3a94ffe77aa340bec722e717 Mon Sep 17 00:00:00 2001 From: Android Dev Date: Fri, 21 Jul 2023 09:10:59 -0700 Subject: [PATCH 05/19] Starter Groovy to Kotlin DSL (#45) --- app/build.gradle | 83 ------------------ app/build.gradle.kts | 87 +++++++++++++++++++ app/proguard-rules.pro | 4 +- .../com/example/inventory/InventoryApp.kt | 3 + .../example/inventory/InventoryApplication.kt | 1 + .../example/inventory/ui/home/HomeScreen.kt | 2 + .../inventory/ui/home/HomeViewModel.kt | 2 +- .../inventory/ui/item/ItemDetailsScreen.kt | 2 + .../inventory/ui/item/ItemEditScreen.kt | 2 + .../inventory/ui/item/ItemEntryScreen.kt | 22 +++-- .../inventory/ui/item/ItemEntryViewModel.kt | 2 +- app/src/main/res/values/themes.xml | 1 - build.gradle => build.gradle.kts | 19 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle => settings.gradle.kts | 5 +- 15 files changed, 133 insertions(+), 104 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts rename build.gradle => build.gradle.kts (60%) rename settings.gradle => settings.gradle.kts (94%) diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 6ebb2d29..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.android' - id 'com.google.devtools.ksp' version "1.8.21-1.0.11" -} - -android { - compileSdk 33 - - defaultConfig { - applicationId "com.example.inventory" - minSdk 24 - targetSdk 33 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary true - } - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - kotlinOptions { - jvmTarget = '17' - freeCompilerArgs += "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api" - } - buildFeatures { - compose true - } - composeOptions { - kotlinCompilerExtensionVersion '1.4.7' - } - packagingOptions { - resources { - excludes += '/META-INF/{AL2.0,LGPL2.1}' - } - } - namespace 'com.example.inventory' -} - -dependencies { - // Import the Compose BOM - implementation platform('androidx.compose:compose-bom:2023.05.00') - - implementation 'androidx.activity:activity-compose:1.7.2' - implementation 'androidx.compose.material3:material3' - implementation "androidx.compose.ui:ui" - implementation "androidx.compose.ui:ui-tooling" - implementation "androidx.compose.ui:ui-tooling-preview" - implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' - implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1" - implementation "androidx.navigation:navigation-compose:2.5.3" - - // Testing - androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" - androidTestImplementation "androidx.test.ext:junit:1.1.5" -} diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..240b3ce5 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("UnstableApiUsage") + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.devtools.ksp") version "1.8.21-1.0.11" +} + +android { + compileSdk = 33 + + defaultConfig { + applicationId = "com.example.inventory" + minSdk = 24 + targetSdk = 33 + 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_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.7" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + namespace = "com.example.inventory" +} + +dependencies { + // Import the Compose BOM + implementation(platform("androidx.compose:compose-bom:2023.06.01")) + implementation("androidx.activity:activity-compose:1.7.2") + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-tooling") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.core:core-ktx:1.10.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1") + implementation("androidx.navigation:navigation-compose:2.6.0") + + // Testing + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.1.5") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb434..2f9dc5a4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/java/com/example/inventory/InventoryApp.kt b/app/src/main/java/com/example/inventory/InventoryApp.kt index 41402228..8053ecf2 100644 --- a/app/src/main/java/com/example/inventory/InventoryApp.kt +++ b/app/src/main/java/com/example/inventory/InventoryApp.kt @@ -14,12 +14,15 @@ * limitations under the License. */ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.example.inventory import android.annotation.SuppressLint import androidx.compose.material.icons.Icons.Filled import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text diff --git a/app/src/main/java/com/example/inventory/InventoryApplication.kt b/app/src/main/java/com/example/inventory/InventoryApplication.kt index 7a6d6542..c0d8c1d5 100644 --- a/app/src/main/java/com/example/inventory/InventoryApplication.kt +++ b/app/src/main/java/com/example/inventory/InventoryApplication.kt @@ -26,6 +26,7 @@ class InventoryApplication : Application() { * AppContainer instance used by the rest of classes to obtain dependencies */ lateinit var container: AppContainer + override fun onCreate() { super.onCreate() container = AppDataContainer(this) diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt index 2fb95501..cc3c530a 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -61,6 +62,7 @@ object HomeDestination : NavigationDestination { /** * Entry route for Home screen */ +@OptIn(ExperimentalMaterial3Api::class) @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun HomeScreen( diff --git a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt index b46e69fb..78086903 100644 --- a/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/home/HomeViewModel.kt @@ -20,7 +20,7 @@ import androidx.lifecycle.ViewModel import com.example.inventory.data.Item /** - * View Model to retrieve all items in the Room database. + * ViewModel to retrieve all items in the Room database. */ class HomeViewModel : ViewModel() { companion object { diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt index 0443a202..5588f985 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemDetailsScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -61,6 +62,7 @@ object ItemDetailsDestination : NavigationDestination { val routeWithArgs = "$route/{$itemIdArg}" } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemDetailsScreen( navigateToEditItem: (Int) -> Unit, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt index 45746a5c..0d5dfd39 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEditScreen.kt @@ -17,6 +17,7 @@ package com.example.inventory.ui.item import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -36,6 +37,7 @@ object ItemEditDestination : NavigationDestination { val routeWithArgs = "$route/{$itemIdArg}" } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemEditScreen( navigateBack: () -> Unit, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt index 68ba7076..be9e8005 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryScreen.kt @@ -24,8 +24,10 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults @@ -49,6 +51,7 @@ object ItemEntryDestination : NavigationDestination { override val titleRes = R.string.item_entry_title } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemEntryScreen( navigateBack: () -> Unit, @@ -104,6 +107,7 @@ fun ItemEntryBody( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemInputForm( itemDetails: ItemDetails, @@ -119,8 +123,10 @@ fun ItemInputForm( value = itemDetails.name, onValueChange = { onValueChange(itemDetails.copy(name = it)) }, label = { Text(stringResource(R.string.item_name_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, @@ -131,8 +137,10 @@ fun ItemInputForm( onValueChange = { onValueChange(itemDetails.copy(price = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), label = { Text(stringResource(R.string.item_price_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), leadingIcon = { Text(Currency.getInstance(Locale.getDefault()).symbol) }, modifier = Modifier.fillMaxWidth(), @@ -144,8 +152,10 @@ fun ItemInputForm( onValueChange = { onValueChange(itemDetails.copy(quantity = it)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), label = { Text(stringResource(R.string.quantity_req)) }, - colors = TextFieldDefaults.outlinedTextFieldColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer + colors = OutlinedTextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + unfocusedContainerColor = MaterialTheme.colorScheme.secondaryContainer, + disabledContainerColor = MaterialTheme.colorScheme.secondaryContainer, ), modifier = Modifier.fillMaxWidth(), enabled = enabled, diff --git a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt index acd502e3..22915048 100644 --- a/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt +++ b/app/src/main/java/com/example/inventory/ui/item/ItemEntryViewModel.kt @@ -24,7 +24,7 @@ import com.example.inventory.data.Item import java.text.NumberFormat /** - * View Model to validate and insert items in the Room database. + * ViewModel to validate and insert items in the Room database. */ class ItemEntryViewModel : ViewModel() { diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 2ff1b923..49473863 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -15,6 +15,5 @@ ~ limitations under the License. --> -