Implemented AssistedInject for details screen

This commit is contained in:
Adrian Kuta 2024-07-29 12:07:13 +02:00
parent 08da2cbbe0
commit aab5f5e0de
25 changed files with 195 additions and 34 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
Pixabay

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

18
.idea/deploymentTargetSelector.xml generated Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2024-07-26T12:17:16.367108Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/adriankuta/.android/avd/Pixel_8_API_34.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

39
.idea/gradle.xml generated Normal file
View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="SCRIPT">
<builds>
<build path="$PROJECT_DIR$/build-logic" name="build-logic">
<projects>
<project path="$PROJECT_DIR$/build-logic" />
<project path="$PROJECT_DIR$/build-logic/convention" />
</projects>
</build>
</builds>
</compositeBuild>
</compositeConfiguration>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/build-logic" />
<option value="$PROJECT_DIR$/build-logic/convention" />
<option value="$PROJECT_DIR$/core" />
<option value="$PROJECT_DIR$/core/ui" />
<option value="$PROJECT_DIR$/data" />
<option value="$PROJECT_DIR$/feature" />
<option value="$PROJECT_DIR$/feature/details" />
<option value="$PROJECT_DIR$/feature/search" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,53 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
</profile>
</component>

6
.idea/kotlinc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.24" />
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

1
build-logic/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.gradle/

View File

@ -1,2 +0,0 @@
#Wed Jul 24 13:11:02 CEST 2024
gradle.version=8.7

View File

@ -23,6 +23,7 @@ fun SearchField(
value = query, value = query,
onValueChange = onQueryChange, onValueChange = onQueryChange,
modifier = modifier, modifier = modifier,
maxLines = 1,
placeholder = { placeholder = {
if (hint != null) { if (hint != null) {
Text(text = hint) Text(text = hint)

View File

@ -1,3 +1,5 @@
import java.util.Properties
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins { plugins {
alias(libs.plugins.convention.android.library) alias(libs.plugins.convention.android.library)
@ -9,10 +11,15 @@ android {
buildFeatures { buildFeatures {
buildConfig = true buildConfig = true
} }
defaultConfig { defaultConfig {
buildConfigField("String", "PIXABAY_API_KEY", "\"<REPLACE_WITH_PIXABAY_API_KEY>\"") val localPropertiesFile = project.rootProject.file("local.properties")
val properties = Properties()
properties.load(localPropertiesFile.inputStream())
val apiKey = properties.getProperty("PIXABAY_API_KEY") ?: ""
buildConfigField("String", "PIXABAY_API_KEY", "\"$apiKey\"")
} }
} }
dependencies { dependencies {

View File

@ -9,10 +9,13 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import dev.adriankuta.pixabay.data.room.AppDatabase import dev.adriankuta.pixabay.data.room.AppDatabase
import dev.adriankuta.pixabay.data.room.dao.PixabayImagesDao import dev.adriankuta.pixabay.data.room.dao.PixabayImagesDao
import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@Module @Module
internal class PersistanceModule { internal class PersistanceModule {
@Singleton
@Provides @Provides
fun provideRoomDb(@ApplicationContext context: Context): AppDatabase { fun provideRoomDb(@ApplicationContext context: Context): AppDatabase {
return Room.databaseBuilder( return Room.databaseBuilder(

View File

@ -75,6 +75,6 @@ internal class PixabayImageRepository @Inject constructor(
companion object { companion object {
const val NETWORK_PAGE_SIZE = 30 const val NETWORK_PAGE_SIZE = 30
const val USE_CACHE_PAGER = false const val USE_CACHE_PAGER = true
} }
} }

View File

@ -14,7 +14,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -34,6 +33,7 @@ import dev.adriankuta.pixabay.core.ui.R.drawable
import dev.adriankuta.pixabay.core.ui.R.string import dev.adriankuta.pixabay.core.ui.R.string
import dev.adriankuta.pixabay.core.ui.StatsItem import dev.adriankuta.pixabay.core.ui.StatsItem
import dev.adriankuta.pixabay.data.model.PixabayImage import dev.adriankuta.pixabay.data.model.PixabayImage
import dev.adriankuta.pixabay.feature.details.di.PhotoDetailsViewModelFactory
@Composable @Composable
@ -41,15 +41,15 @@ fun PhotoDetailRoute(
photoId: Int, photoId: Int,
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: PhotoDetailViewModel = hiltViewModel() viewModel: PhotoDetailViewModel = hiltViewModel(
creationCallback = { factory: PhotoDetailsViewModelFactory ->
factory.create(photoId)
}
)
) { ) {
val uiState by viewModel.uiState.collectAsState() val uiState by viewModel.uiState.collectAsState()
LaunchedEffect(photoId) {
viewModel.loadImage(photoId)
}
PhotoDetailScreen( PhotoDetailScreen(
state = uiState, state = uiState,
modifier = modifier modifier = modifier

View File

@ -1,30 +1,25 @@
@file:OptIn(ExperimentalCoroutinesApi::class)
package dev.adriankuta.pixabay.feature.details package dev.adriankuta.pixabay.feature.details
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dev.adriankuta.pixabay.data.repository.ImageRepository import dev.adriankuta.pixabay.data.repository.ImageRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi import dev.adriankuta.pixabay.feature.details.di.PhotoDetailsViewModelFactory
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject
@HiltViewModel @HiltViewModel(assistedFactory = PhotoDetailsViewModelFactory::class)
class PhotoDetailViewModel @Inject constructor( class PhotoDetailViewModel @AssistedInject constructor(
private val imageRepository: ImageRepository private val imageRepository: ImageRepository,
@Assisted private val imageId: Int
) : ViewModel() { ) : ViewModel() {
private val _idToLoad = MutableStateFlow<Int?>(null) private val loadedData = flow {
emit(imageRepository.searchImageById(imageId))
private val loadedData = _idToLoad
.filterNotNull()
.map { id ->
imageRepository.searchImageById(id)
} }
val uiState = loadedData val uiState = loadedData
@ -34,9 +29,4 @@ class PhotoDetailViewModel @Inject constructor(
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000), started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
initialValue = PhotoDetailUiState.Loading initialValue = PhotoDetailUiState.Loading
) )
fun loadImage(id: Int) {
_idToLoad.value = id
}
} }

View File

@ -0,0 +1,9 @@
package dev.adriankuta.pixabay.feature.details.di
import dagger.assisted.AssistedFactory
import dev.adriankuta.pixabay.feature.details.PhotoDetailViewModel
@AssistedFactory
internal interface PhotoDetailsViewModelFactory {
fun create(imageId: Int): PhotoDetailViewModel
}

View File

@ -1,13 +1,13 @@
[versions] [versions]
androidxNavigation = "2.7.7" androidxNavigation = "2.7.7"
androidGradlePlugin = "8.1.4" androidGradlePlugin = "8.1.4"
agp = "8.6.0-beta01" agp = "8.6.0-beta02"
coilCompose = "2.6.0" coilCompose = "2.6.0"
composeCompiler = "1.5.14" composeCompiler = "1.5.14"
kotlin = "1.9.24" kotlin = "1.9.24"
kotlinSerialization = "2.0.0" kotlinSerialization = "2.0.0"
coreKtx = "1.13.1" coreKtx = "1.13.1"
androidxHilt = "1.0.0" androidxHilt = "1.2.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.2.1" junitVersion = "1.2.1"
espressoCore = "3.6.1" espressoCore = "3.6.1"