Skip to content
Prev Previous commit
Next Next commit
[Crane] Adds DetailsViewModel with AssistedInjection
  • Loading branch information
Manuel Vivo committed Oct 20, 2020
commit 9b515d6f025d0b7ca2c1b177952f65037141c374
9 changes: 9 additions & 0 deletions Crane/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ android {
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.compose.samples.crane.CustomTestRunner"

javaCompileOptions {
annotationProcessorOptions {
arguments["dagger.hilt.disableModulesHaveInstallInCheck"] = "true"
}
}

manifestPlaceholders = [ googleMapsKey : properties.getProperty("google.maps.key", "") ]
}

Expand Down Expand Up @@ -113,8 +119,10 @@ dependencies {
implementation Libs.AndroidX.Lifecycle.viewModelKtx
implementation Libs.Hilt.android
implementation Libs.Hilt.AndroidX.viewModel
compileOnly Libs.AssistedInjection.dagger
kapt Libs.Hilt.compiler
kapt Libs.Hilt.AndroidX.compiler
kapt Libs.AssistedInjection.processor

androidTestImplementation Libs.JUnit.junit
androidTestImplementation Libs.AndroidX.Test.runner
Expand All @@ -128,4 +136,5 @@ dependencies {
androidTestImplementation Libs.Hilt.testing
kaptAndroidTest Libs.Hilt.compiler
kaptAndroidTest Libs.Hilt.AndroidX.compiler
kaptAndroidTest Libs.AssistedInjection.processor
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package androidx.compose.samples.crane.details

import androidx.compose.samples.crane.R
import androidx.compose.samples.crane.data.DestinationsRepository
import androidx.compose.samples.crane.data.ExploreModel
import androidx.compose.samples.crane.data.MADRID
import androidx.compose.samples.crane.di.DispatchersModule
Expand All @@ -35,16 +36,22 @@ import com.google.android.libraries.maps.model.LatLng
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.util.concurrent.CountDownLatch
import javax.inject.Inject

@UninstallModules(DispatchersModule::class)
@HiltAndroidTest
class DetailsActivityTest {

private val expectedDescription = "description"
private val testExploreModel = ExploreModel(MADRID, expectedDescription, "imageUrl")
@Inject
lateinit var destinationsRepository: DestinationsRepository
lateinit var cityDetails: ExploreModel

private val city = MADRID
private val testExploreModel = ExploreModel(city, "description", "imageUrl")

@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
Expand All @@ -59,10 +66,16 @@ class DetailsActivityTest {
)
)

@Before
fun setUp() {
hiltRule.inject()
cityDetails = destinationsRepository.getDestination(MADRID.name)!!
}

@Test
fun mapView_cameraPositioned() {
composeTestRule.onNodeWithText(MADRID.nameToDisplay).assertIsDisplayed()
composeTestRule.onNodeWithText(expectedDescription).assertIsDisplayed()
composeTestRule.onNodeWithText(cityDetails.city.nameToDisplay).assertIsDisplayed()
composeTestRule.onNodeWithText(cityDetails.description).assertIsDisplayed()
onView(withId(R.id.map)).check(matches(isDisplayed()))

var cameraPosition: CameraPosition? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ package androidx.compose.samples.crane.data
import javax.inject.Inject

class DestinationsRepository @Inject constructor(
destinationsLocalDataSource: DestinationsLocalDataSource
private val destinationsLocalDataSource: DestinationsLocalDataSource
) {
val destinations = destinationsLocalDataSource.craneDestinations
val hotels = destinationsLocalDataSource.craneHotels
val restaurants = destinationsLocalDataSource.craneRestaurants

fun getDestination(cityName: String): ExploreModel? {
return destinationsLocalDataSource.craneDestinations.firstOrNull {
it.city.name == cityName
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ import com.google.android.libraries.maps.MapView
import com.google.android.libraries.maps.model.LatLng
import com.google.android.libraries.maps.model.MarkerOptions
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

private const val DETAILS_NAME = "DETAILS_NAME"
private const val DETAILS_DESCRIPTION = "DETAILS_DESCRIPTION"
private const val DETAILS_LATITUDE = "DETAILS_LATITUDE"
private const val DETAILS_LONGITUDE = "DETAILS_LONGITUDE"
private const val DETAILS_CITY_NAME = "DETAILS_CITY_NAME"

fun launchDetailsActivity(context: Context, item: ExploreModel) {
context.startActivity(createDetailsActivityIntent(context, item))
Expand All @@ -60,57 +58,62 @@ fun launchDetailsActivity(context: Context, item: ExploreModel) {
@VisibleForTesting
fun createDetailsActivityIntent(context: Context, item: ExploreModel): Intent {
val intent = Intent(context, DetailsActivity::class.java)
intent.putExtra(DETAILS_NAME, item.city.nameToDisplay)
intent.putExtra(DETAILS_DESCRIPTION, item.description)
intent.putExtra(DETAILS_LATITUDE, item.city.latitude)
intent.putExtra(DETAILS_LONGITUDE, item.city.longitude)
intent.putExtra(DETAILS_CITY_NAME, item.city.name)
return intent
}

data class DetailsActivityArg(
val name: String,
val description: String,
val latitude: String,
val longitude: String
val cityName: String
)

@AndroidEntryPoint
class DetailsActivity : ComponentActivity() {

@Inject
lateinit var viewModelFactory: DetailsViewModel.AssistedFactory

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val args = DetailsActivityArg(
name = intent.getStringExtra(DETAILS_NAME)!!,
description = intent.getStringExtra(DETAILS_DESCRIPTION)!!,
latitude = intent.getStringExtra(DETAILS_LATITUDE)!!,
longitude = intent.getStringExtra(DETAILS_LONGITUDE)!!
)
val args = DetailsActivityArg(intent.getStringExtra(DETAILS_CITY_NAME)!!)

setContent {
CraneScaffold {
DetailsScreen(args = args)
DetailsScreen(args, viewModelFactory, onErrorLoading = { finish() })
}
}
}
}

@Composable
fun DetailsScreen(args: DetailsActivityArg) {
fun DetailsScreen(
args: DetailsActivityArg,
viewModelFactory: DetailsViewModel.AssistedFactory,
onErrorLoading: () -> Unit
) {
val viewModel: DetailsViewModel = viewModelFactory.create(args.cityName)
if (viewModel.cityDetails != null) {
DetailsContent(viewModel.cityDetails)
} else {
onErrorLoading()
}
}

@Composable
fun DetailsContent(exploreModel: ExploreModel) {
Column(verticalArrangement = Arrangement.Center) {
Spacer(Modifier.preferredHeight(32.dp))
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = args.name,
text = exploreModel.city.nameToDisplay,
style = MaterialTheme.typography.h4
)
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = args.description,
text = exploreModel.description,
style = MaterialTheme.typography.h6
)
Spacer(Modifier.preferredHeight(16.dp))
CityMapView(args.latitude, args.longitude)
CityMapView(exploreModel.city.latitude, exploreModel.city.longitude)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2020 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 androidx.compose.samples.crane.details

import androidx.compose.samples.crane.data.DestinationsRepository
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import com.squareup.inject.assisted.dagger2.AssistedModule
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent

class DetailsViewModel @AssistedInject constructor(
destinationsRepository: DestinationsRepository,
@Assisted cityName: String
) : ViewModel() {

val cityDetails = destinationsRepository.getDestination(cityName)

@AssistedInject.Factory
interface AssistedFactory {
fun create(cityName: String): DetailsViewModel
}

@Suppress("UNCHECKED_CAST")
companion object {
fun provideFactory(
assistedFactory: AssistedFactory,
cityName: String
): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return assistedFactory.create(cityName) as T
}
}
}
}

@InstallIn(ActivityRetainedComponent::class)
@AssistedModule
@Module(includes = [AssistedInject_AssistedInjectModule::class])
interface AssistedInjectModule
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ object Libs {
private const val version = "4.13"
const val junit = "junit:junit:$version"
}

object AssistedInjection {
private const val version = "0.5.2"

const val dagger = "com.squareup.inject:assisted-inject-annotations-dagger2:$version"
const val processor = "com.squareup.inject:assisted-inject-processor-dagger2:$version"
}
}

object Urls {
Expand Down