Implement view model provider, local database

This commit is contained in:
Joren 2024-04-29 23:19:14 +02:00
parent ec25b03e98
commit 77d7056962
Signed by untrusted user who does not match committer: Joren
GPG Key ID: 280E33DFBC0F1B55
9 changed files with 77 additions and 10 deletions

View File

@ -4,6 +4,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name=".PokeSearch"
android:allowBackup="true" android:allowBackup="true"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -0,0 +1,21 @@
package com.ti.mobpo
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel
object AppViewModelProvider {
val Factory = viewModelFactory {
initializer {
PokeSearchViewModel(
pokesearchApplication().appContainer.favouritesRepository
)
}
}
}
fun CreationExtras.pokesearchApplication(): PokeSearch =
(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as PokeSearch)

View File

@ -0,0 +1,19 @@
package com.ti.mobpo
import android.app.Application
import com.ti.mobpo.data.AppContainer
import com.ti.mobpo.data.AppDataContainer
class PokeSearch : Application() {
lateinit var appContainer: AppContainer
private set
override fun onCreate() {
super.onCreate()
appContainer = createAppContainer()
}
private fun createAppContainer(): AppContainer {
return AppDataContainer(this)
}
}

View File

@ -17,4 +17,7 @@ interface FavouriteDao {
@Query("SELECT id FROM favourites") @Query("SELECT id FROM favourites")
fun getAllFavoriteIds(): Flow<List<Int>> fun getAllFavoriteIds(): Flow<List<Int>>
@Query("SELECT EXISTS(SELECT 1 FROM favourites WHERE id = :id LIMIT 1)")
suspend fun isFavourite(id: Int): Boolean
} }

View File

@ -20,4 +20,10 @@ interface FavouritesRepository {
* Delete item from the data source * Delete item from the data source
*/ */
suspend fun deleteItem(item: Favourite) suspend fun deleteItem(item: Favourite)
/**
* Check if the item is a favourite
*/
suspend fun isFavourite(id: Int): Boolean
} }

View File

@ -13,4 +13,8 @@ class OfflineFavouritesRepository(private val favouriteDao: FavouriteDao) : Favo
override suspend fun deleteItem(item: Favourite) { override suspend fun deleteItem(item: Favourite) {
favouriteDao.delete(item) favouriteDao.delete(item)
} }
override suspend fun isFavourite(id: Int): Boolean {
return favouriteDao.isFavourite(id)
}
} }

View File

@ -20,6 +20,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
@ -30,7 +31,6 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.ti.mobpo.ui.pokesearch.PokeSearchViewModel
import com.ti.mobpo.ui.screens.Favourites import com.ti.mobpo.ui.screens.Favourites
import com.ti.mobpo.ui.screens.PokeSearchScreen import com.ti.mobpo.ui.screens.PokeSearchScreen
@ -61,8 +61,6 @@ fun Navigation() {
val updatedSelectedIndex = items.indexOfFirst { it.route == currentRoute } val updatedSelectedIndex = items.indexOfFirst { it.route == currentRoute }
selectedItemIndex = rememberUpdatedState(updatedSelectedIndex).value selectedItemIndex = rememberUpdatedState(updatedSelectedIndex).value
val pokeSearchViewModel: PokeSearchViewModel = viewModel()
Scaffold ( Scaffold (
bottomBar = { bottomBar = {
NavigationBar { NavigationBar {
@ -106,7 +104,7 @@ fun Navigation() {
ExitTransition.None ExitTransition.None
}) { }) {
composable(route = Screen.PokeSearch.route) { composable(route = Screen.PokeSearch.route) {
PokeSearchScreen(pokeSearchViewModel) PokeSearchScreen()
} }
composable( composable(
route = Screen.Favourites.route) { route = Screen.Favourites.route) {
@ -124,7 +122,6 @@ fun Navigation() {
@Composable @Composable
fun NavigationPreview() { fun NavigationPreview() {
Surface { Surface {
val pokeSearchViewModel: PokeSearchViewModel = viewModel() PokeSearchScreen()
PokeSearchScreen(pokeSearchViewModel)
} }
} }

View File

@ -2,6 +2,8 @@ package com.ti.mobpo.ui.pokesearch
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.ti.mobpo.data.Favourite
import com.ti.mobpo.data.FavouritesRepository
import com.ti.mobpo.model.PokemonDetails import com.ti.mobpo.model.PokemonDetails
import com.ti.mobpo.network.PokeApi import com.ti.mobpo.network.PokeApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -12,7 +14,7 @@ import java.io.IOException
class PokeSearchViewModel : ViewModel() { class PokeSearchViewModel(private val favouritesRepository: FavouritesRepository) : ViewModel() {
private val service = PokeApi.retrofitService; private val service = PokeApi.retrofitService;
private val _pokemonDetails = MutableStateFlow<List<PokemonDetails>?>(null) private val _pokemonDetails = MutableStateFlow<List<PokemonDetails>?>(null)
@ -26,7 +28,8 @@ class PokeSearchViewModel : ViewModel() {
val detailsList = mutableListOf<PokemonDetails>() val detailsList = mutableListOf<PokemonDetails>()
for (pokemonSpecies in filteredList) { for (pokemonSpecies in filteredList) {
val details = service.getPokemonDetails(extractPokemonId(pokemonSpecies.url)) val details = service.getPokemonDetails(extractPokemonId(pokemonSpecies.url))
detailsList.add(details) val isFavorite = favouritesRepository.isFavourite(details.id)
detailsList.add(details.copy(isFavorite = isFavorite))
} }
_pokemonDetails.value = detailsList _pokemonDetails.value = detailsList
} catch (e: IOException) { } catch (e: IOException) {
@ -48,5 +51,17 @@ class PokeSearchViewModel : ViewModel() {
pokemon pokemon
} }
} }
viewModelScope.launch {
if (_pokemonDetails.value != null) {
val pokemon = _pokemonDetails.value!!.find { it.id == pokemonId }
pokemon?.let {
if (it.isFavorite) {
favouritesRepository.insertItem(Favourite(it.id, it.name))
} else {
favouritesRepository.deleteItem(Favourite(it.id, it.name))
}
}
}
}
} }
} }

View File

@ -45,12 +45,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.ti.mobpo.model.PokemonDetails import com.ti.mobpo.model.PokemonDetails
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import com.ti.mobpo.AppViewModelProvider
import com.ti.mobpo.capitalizeFirstLetterAfterHyphens import com.ti.mobpo.capitalizeFirstLetterAfterHyphens
import com.ti.mobpo.ui.theme.MobileSecurityTheme import com.ti.mobpo.ui.theme.MobileSecurityTheme
@Composable @Composable
fun PokeSearchScreen(pokeSearchViewModel: PokeSearchViewModel) { fun PokeSearchScreen(viewModel: PokeSearchViewModel = viewModel(factory = AppViewModelProvider.Factory)) {
PokeSearch(pokeSearchViewModel) PokeSearch(viewModel)
} }
@Composable @Composable