Use hiltViewModel for creating ViewModels with parameters in new Screen architecture (#776)

* Add hilt-navigation-compose dependency

* AccountScreen: use hiltViewModel

* Use hiltViewModel for CollectionScreen and CreateCollectionScreens
This commit is contained in:
Ricki Hirner 2024-05-03 14:46:46 +02:00 committed by GitHub
parent 9005121b52
commit e6eb90861e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 39 additions and 116 deletions

View File

@ -148,6 +148,7 @@ dependencies {
implementation(libs.androidx.constraintLayout)
implementation(libs.androidx.core)
implementation(libs.androidx.fragment)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.base)

View File

@ -10,20 +10,11 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
@AndroidEntryPoint
class AccountActivity : AppCompatActivity() {
@EntryPoint
@InstallIn(ActivityComponent::class)
interface AccountScreenEntryPoint {
fun accountModelAssistedFactory(): AccountScreenModel.Factory
}
companion object {
const val EXTRA_ACCOUNT = "account"
}

View File

@ -1,7 +1,6 @@
import android.Manifest
import android.accounts.Account
import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.ExperimentalFoundationApi
@ -61,8 +60,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import at.bitfire.davdroid.R
@ -70,7 +69,6 @@ import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.PermissionsActivity
import at.bitfire.davdroid.ui.account.AccountActivity
import at.bitfire.davdroid.ui.account.AccountProgress
import at.bitfire.davdroid.ui.account.AccountScreenModel
import at.bitfire.davdroid.ui.account.CollectionsList
@ -79,7 +77,6 @@ import at.bitfire.davdroid.ui.composable.ActionCard
import at.bitfire.davdroid.util.TaskUtils
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.launch
@Composable
@ -92,16 +89,17 @@ fun AccountScreen(
onNavUp: () -> Unit,
onFinish: () -> Unit
) {
val context = LocalContext.current as Activity
val entryPoint = EntryPointAccessors.fromActivity(context, AccountActivity.AccountScreenEntryPoint::class.java)
val model = viewModel<AccountScreenModel>(
factory = AccountScreenModel.factoryFromAccount(entryPoint.accountModelAssistedFactory(), account)
val model: AccountScreenModel = hiltViewModel(
creationCallback = { factory: AccountScreenModel.Factory ->
factory.create(account)
}
)
val addressBooksPager by model.addressBooksPager.collectAsState(null)
val calendarsPager by model.calendarsPager.collectAsState(null)
val subscriptionsPager by model.webcalPager.collectAsState(null)
val context = LocalContext.current
AccountScreen(
accountName = account.name,
error = model.error,

View File

@ -30,6 +30,7 @@ import at.bitfire.davdroid.util.TaskUtils
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@ -39,15 +40,16 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import java.util.logging.Level
@HiltViewModel(assistedFactory = AccountScreenModel.Factory::class)
class AccountScreenModel @AssistedInject constructor(
@Assisted val account: Account,
val context: Application,
private val accountRepository: AccountRepository,
private val collectionRepository: DavCollectionRepository,
serviceRepository: DavServiceRepository,
accountProgressUseCase: AccountProgressUseCase,
getBindableHomesetsFromServiceUseCase: GetBindableHomeSetsFromServiceUseCase,
getServiceCollectionPagerUseCase: GetServiceCollectionPagerUseCase,
@Assisted val account: Account
getServiceCollectionPagerUseCase: GetServiceCollectionPagerUseCase
): ViewModel() {
@AssistedFactory
@ -55,15 +57,6 @@ class AccountScreenModel @AssistedInject constructor(
fun create(account: Account): AccountScreenModel
}
companion object {
fun factoryFromAccount(assistedFactory: Factory, account: Account) = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return assistedFactory.create(account) as T
}
}
}
/** whether the account is invalid and the screen shall be closed */
val invalidAccount = accountRepository.getAllFlow().map { accounts ->
!accounts.contains(account)

View File

@ -10,20 +10,11 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.TaskStackBuilder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
@AndroidEntryPoint
class CollectionActivity: AppCompatActivity() {
@EntryPoint
@InstallIn(ActivityComponent::class)
interface CollectionEntryPoint {
fun collectionModelAssistedFactory(): CollectionScreenModel.Factory
}
companion object {
const val EXTRA_ACCOUNT = "account"
const val EXTRA_COLLECTION_ID = "collection_id"

View File

@ -4,7 +4,6 @@
package at.bitfire.davdroid.ui.account
import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -21,7 +20,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.AccountBox
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.CloudSync
import androidx.compose.material.icons.filled.DeleteForever
import androidx.compose.material.icons.filled.DoNotDisturbOn
@ -47,19 +45,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.R
import at.bitfire.davdroid.repository.DavSyncStatsRepository
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.composable.ExceptionInfoDialog
import dagger.hilt.android.EntryPointAccessors
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
@ -72,10 +68,10 @@ fun CollectionScreen(
onFinish: () -> Unit,
onNavUp: () -> Unit
) {
val context = LocalContext.current as Activity
val entryPoint = EntryPointAccessors.fromActivity(context, CollectionActivity.CollectionEntryPoint::class.java)
val model = viewModel<CollectionScreenModel>(
factory = CollectionScreenModel.factoryFromCollection(entryPoint.collectionModelAssistedFactory(), collectionId)
val model: CollectionScreenModel = hiltViewModel(
creationCallback = { factory: CollectionScreenModel.Factory ->
factory.create(collectionId)
}
)
val collectionOrNull by model.collection.collectAsStateWithLifecycle(null)

View File

@ -8,7 +8,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.repository.DavCollectionRepository
@ -17,6 +16,7 @@ import at.bitfire.davdroid.util.lastSegment
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
@ -25,6 +25,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@HiltViewModel(assistedFactory = CollectionScreenModel.Factory::class)
class CollectionScreenModel @AssistedInject constructor(
@Assisted val collectionId: Long,
db: AppDatabase,
@ -37,15 +38,6 @@ class CollectionScreenModel @AssistedInject constructor(
fun create(collectionId: Long): CollectionScreenModel
}
companion object {
fun factoryFromCollection(assistedFactory: Factory, collectionId: Long) = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return assistedFactory.create(collectionId) as T
}
}
}
var invalid by mutableStateOf(false)
val collection = collectionRepository.getFlow(collectionId)
.map {

View File

@ -10,20 +10,11 @@ import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.TaskStackBuilder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
@AndroidEntryPoint
class CreateAddressBookActivity: AppCompatActivity() {
@EntryPoint
@InstallIn(ActivityComponent::class)
interface CreateAddressBookEntryPoint {
fun createAddressBookModelAssistedFactory(): CreateAddressBookModel.Factory
}
companion object {
const val EXTRA_ACCOUNT = "account"
}

View File

@ -9,21 +9,22 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.repository.DavHomeSetRepository
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
@HiltViewModel(assistedFactory = CreateAddressBookModel.Factory::class)
class CreateAddressBookModel @AssistedInject constructor(
@Assisted val account: Account,
private val collectionRepository: DavCollectionRepository,
homeSetRepository: DavHomeSetRepository,
@Assisted val account: Account
homeSetRepository: DavHomeSetRepository
): ViewModel() {
@AssistedFactory
@ -31,16 +32,6 @@ class CreateAddressBookModel @AssistedInject constructor(
fun create(account: Account): CreateAddressBookModel
}
companion object {
fun factoryFromAccount(assistedFactory: Factory, account: Account) = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return assistedFactory.create(account) as T
}
}
}
val addressBookHomeSets = homeSetRepository.getAddressBookHomeSetsFlow(account)

View File

@ -5,7 +5,6 @@
package at.bitfire.davdroid.ui.account
import android.accounts.Account
import android.app.Activity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -31,18 +30,16 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.composable.ExceptionInfoDialog
import dagger.hilt.android.EntryPointAccessors
import okhttp3.HttpUrl.Companion.toHttpUrl
@Composable
@ -51,10 +48,10 @@ fun CreateAddressBookScreen(
onNavUp: () -> Unit = {},
onFinish: () -> Unit = {}
) {
val context = LocalContext.current as Activity
val entryPoint = EntryPointAccessors.fromActivity(context, CreateAddressBookActivity.CreateAddressBookEntryPoint::class.java)
val model = viewModel<CreateAddressBookModel>(
factory = CreateAddressBookModel.factoryFromAccount(entryPoint.createAddressBookModelAssistedFactory(), account)
val model: CreateAddressBookModel = hiltViewModel(
creationCallback = { factory: CreateAddressBookModel.Factory ->
factory.create(account)
}
)
val uiState = model.uiState

View File

@ -11,20 +11,11 @@ import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.TaskStackBuilder
import at.bitfire.davdroid.ui.AppTheme
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.components.ActivityComponent
@AndroidEntryPoint
class CreateCalendarActivity: AppCompatActivity() {
@EntryPoint
@InstallIn(ActivityComponent::class)
interface CreateCalendarEntryPoint {
fun createCalendarModelAssistedFactory(): CreateCalendarModel.Factory
}
companion object {
const val EXTRA_ACCOUNT = "account"
}

View File

@ -9,7 +9,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.repository.DavCollectionRepository
import at.bitfire.davdroid.repository.DavHomeSetRepository
@ -17,6 +16,7 @@ import at.bitfire.ical4android.Css3Color
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
@ -30,10 +30,11 @@ import java.time.format.TextStyle
import java.util.Locale
import java.util.TimeZone
@HiltViewModel(assistedFactory = CreateCalendarModel.Factory::class)
class CreateCalendarModel @AssistedInject constructor(
@Assisted val account: Account,
private val collectionRepository: DavCollectionRepository,
homeSetRepository: DavHomeSetRepository,
@Assisted val account: Account
homeSetRepository: DavHomeSetRepository
): ViewModel() {
@AssistedFactory
@ -41,16 +42,6 @@ class CreateCalendarModel @AssistedInject constructor(
fun create(account: Account): CreateCalendarModel
}
companion object {
fun factoryFromAccount(assistedFactory: Factory, account: Account) = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return assistedFactory.create(account) as T
}
}
}
val calendarHomeSets = homeSetRepository.getCalendarHomeSetsFlow(account)
data class TimeZoneInfo(

View File

@ -5,7 +5,6 @@
package at.bitfire.davdroid.ui.account
import android.accounts.Account
import android.app.Activity
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
@ -53,15 +52,14 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.R
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.ui.AppTheme
import at.bitfire.davdroid.ui.composable.ExceptionInfoDialog
import at.bitfire.davdroid.ui.widget.CalendarColorPickerDialog
import at.bitfire.ical4android.Css3Color
import dagger.hilt.android.EntryPointAccessors
import okhttp3.HttpUrl.Companion.toHttpUrl
@Composable
@ -70,10 +68,10 @@ fun CreateCalendarScreen(
onFinish: () -> Unit,
onNavUp: () -> Unit
) {
val context = LocalContext.current as Activity
val entryPoint = EntryPointAccessors.fromActivity(context, CreateCalendarActivity.CreateCalendarEntryPoint::class.java)
val model = viewModel<CreateCalendarModel>(
factory = CreateCalendarModel.factoryFromAccount(entryPoint.createCalendarModelAssistedFactory(), account)
val model: CreateCalendarModel = hiltViewModel(
creationCallback = { factory: CreateCalendarModel.Factory ->
factory.create(account)
}
)
val uiState = model.uiState
@ -203,7 +201,8 @@ fun CreateCalendarScreen(
}
.size(48.dp)
.semantics {
contentDescription = context.getString(R.string.create_collection_color)
contentDescription =
context.getString(R.string.create_collection_color)
}
)
if (showColorPicker) {

View File

@ -70,6 +70,7 @@ androidx-constraintLayout = { module = "androidx.constraintlayout:constraintlayo
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-fragment = { module = "androidx.fragment:fragment-ktx", version.ref = "androidx-fragment" }
androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "androidx-hilt" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "androidx-hilt" }
androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "androidx-hilt" }
androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel-base = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }