Skip to content
Next Next commit
[Crane] Adds Hilt to the project
  • Loading branch information
Manuel Vivo committed Oct 20, 2020
commit ffb504f9e38a4e15c53bf20c9630b62e877825dd
26 changes: 24 additions & 2 deletions Crane/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import com.example.crane.buildsrc.Libs
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}

// Reads the Google maps key that is used in the AndroidManifest
Expand All @@ -36,7 +38,7 @@ android {
versionCode 1
versionName "1.0"
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.compose.samples.crane.CustomTestRunner"

manifestPlaceholders = [ googleMapsKey : properties.getProperty("google.maps.key", "") ]
}
Expand Down Expand Up @@ -81,14 +83,22 @@ android {
resValues false
shaders false
}

composeOptions {
kotlinCompilerVersion Libs.Kotlin.version
kotlinCompilerExtensionVersion Libs.AndroidX.Compose.version
}

packagingOptions {
exclude "META-INF/licenses/**"
exclude "META-INF/AL2.0"
exclude "META-INF/LGPL2.1"
}
}

dependencies {
implementation Libs.Kotlin.stdlib
implementation Libs.Kotlin.Coroutines.android
implementation Libs.googleMaps

implementation Libs.AndroidX.Compose.runtime
Expand All @@ -98,12 +108,24 @@ dependencies {
implementation Libs.AndroidX.Compose.layout
implementation Libs.AndroidX.Compose.animation
implementation Libs.AndroidX.UI.tooling

implementation Libs.Accompanist.coil

implementation Libs.AndroidX.Lifecycle.viewModelKtx
implementation Libs.Hilt.android
implementation Libs.Hilt.AndroidX.viewModel
kapt Libs.Hilt.compiler
kapt Libs.Hilt.AndroidX.compiler

androidTestImplementation Libs.JUnit.junit
androidTestImplementation Libs.AndroidX.Test.runner
androidTestImplementation Libs.AndroidX.Test.espressoCore
androidTestImplementation Libs.AndroidX.Test.rules
androidTestImplementation Libs.AndroidX.Test.Ext.junit
androidTestImplementation Libs.Kotlin.Coroutines.test
androidTestImplementation Libs.AndroidX.Compose.uiTest
androidTestImplementation Libs.Hilt.android
androidTestImplementation Libs.Hilt.AndroidX.viewModel
androidTestImplementation Libs.Hilt.testing
kaptAndroidTest Libs.Hilt.compiler
kaptAndroidTest Libs.Hilt.AndroidX.compiler
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class CustomTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,66 +17,71 @@
package androidx.compose.samples.crane.calendar

import androidx.compose.material.Surface
import androidx.compose.samples.crane.base.ServiceLocator
import androidx.compose.samples.crane.calendar.model.CalendarDay
import androidx.compose.samples.crane.calendar.model.CalendarMonth
import androidx.compose.samples.crane.calendar.model.DaySelected
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.FirstDay
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.FirstLastDay
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.LastDay
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.NoSelected
import androidx.compose.samples.crane.calendar.model.DaySelectedStatus.Selected
import androidx.compose.samples.crane.data.DatesRepository
import androidx.compose.samples.crane.di.DispatchersModule
import androidx.compose.samples.crane.ui.CraneTheme
import androidx.ui.test.ComposeTestRule
import androidx.ui.test.SemanticsMatcher
import androidx.ui.test.assertLabelEquals
import androidx.ui.test.createComposeRule
import androidx.ui.test.createAndroidComposeRule
import androidx.ui.test.onNodeWithLabel
import androidx.ui.test.performClick
import androidx.ui.test.performScrollTo
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import javax.inject.Inject

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

@get:Rule
val composeTestRule = createComposeRule(disableTransitions = true)

var dateSelected = ""
private val onDayClicked: (CalendarDay, CalendarMonth) -> Unit = { day, month ->
dateSelected = "${month.name} ${day.value}"
ServiceLocator.datesSelected.daySelected(
DaySelected(
day = day.value.toInt(),
month = month
)
)
}
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<CalendarActivity>()

@Inject
lateinit var datesRepository: DatesRepository

@Before
fun setUp() {
hiltRule.inject()

composeTestRule.setContent {
CraneTheme {
Surface {
Calendar(onDayClicked)
CalendarScreen(onBackPressed = {})
}
}
}
}

/**
* The same instance of the Activity is used in all tests. Therefore, each test gets the same
* Hilt components set. As DatesRepository is a singleton, we need to clear shared data.
*/
@After
fun tearDown() {
ServiceLocator.datesSelected.clearDates()
datesRepository.datesSelected.clearDates()
}

@Test
fun scrollsToTheBottom() {
composeTestRule.onNodeWithLabel("January 1").assertExists()
composeTestRule.onNodeWithLabel("December 31").performScrollTo().performClick()
assert(dateSelected == "December 31")
assert(datesRepository.datesSelected.toString() == "Dec 31")
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package androidx.compose.samples.crane.details
import androidx.compose.samples.crane.R
import androidx.compose.samples.crane.data.ExploreModel
import androidx.compose.samples.crane.data.MADRID
import androidx.compose.samples.crane.di.DispatchersModule
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
Expand All @@ -31,16 +32,24 @@ import androidx.ui.test.onNodeWithText
import com.google.android.libraries.maps.MapView
import com.google.android.libraries.maps.model.CameraPosition
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.Rule
import org.junit.Test
import java.util.concurrent.CountDownLatch

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

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

@get:Rule
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1)
val composeTestRule = AndroidComposeTestRule(
ActivityScenarioRule<DetailsActivity>(
createDetailsActivityIntent(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.
*/

@file:Suppress("DEPRECATION")

package androidx.compose.samples.crane.di

import android.os.AsyncTask
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asCoroutineDispatcher

@OptIn(ExperimentalCoroutinesApi::class)
@Module
@InstallIn(ApplicationComponent::class)
class TestDispatchersModule {

@Provides
@DefaultDispatcher
fun provideDefaultDispatcher(): CoroutineDispatcher =
AsyncTask.THREAD_POOL_EXECUTOR.asCoroutineDispatcher()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@
package androidx.compose.samples.crane.home

import androidx.compose.material.Surface
import androidx.compose.samples.crane.di.DispatchersModule
import androidx.compose.samples.crane.ui.CraneTheme
import androidx.ui.test.createComposeRule
import androidx.ui.test.createAndroidComposeRule
import androidx.ui.test.onNodeWithText
import androidx.ui.test.performClick
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

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

@get:Rule
val composeTestRule = createComposeRule(disableTransitions = true)
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)

@get:Rule(order = 1)
val composeTestRule = createAndroidComposeRule<MainActivity>()

@Before
fun setUp() {
Expand Down
1 change: 1 addition & 0 deletions Crane/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".CraneApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
* limitations under the License.
*/

package androidx.compose.samples.crane.base
package androidx.compose.samples.crane

import androidx.compose.samples.crane.calendar.model.DatesSelectedState
import android.app.Application
import dagger.hilt.android.HiltAndroidApp

object ServiceLocator {
val datesSelected = DatesSelectedState()
}
@HiltAndroidApp
class CraneApplication : Application()
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,20 @@ import androidx.compose.material.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.samples.crane.R
import androidx.compose.samples.crane.base.CraneScaffold
import androidx.compose.samples.crane.base.ServiceLocator
import androidx.compose.samples.crane.calendar.model.CalendarDay
import androidx.compose.samples.crane.calendar.model.CalendarMonth
import androidx.compose.samples.crane.calendar.model.DaySelected
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.viewinterop.viewModel
import dagger.hilt.android.AndroidEntryPoint

fun launchCalendarActivity(context: Context) {
val intent = Intent(context, CalendarActivity::class.java)
context.startActivity(intent)
}

@AndroidEntryPoint
class CalendarActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -57,27 +59,32 @@ class CalendarActivity : ComponentActivity() {
}
}

// Extracted out to a separate variable. If this lambda is used as a trailing lambda in the
// Calendar function, it recomposes the whole Calendar view when clicked on it.
private val onDayClicked: (CalendarDay, CalendarMonth) -> Unit = { calendarDay, calendarMonth ->
ServiceLocator.datesSelected.daySelected(
DaySelected(
day = calendarDay.value.toInt(),
month = calendarMonth
)
@Composable
fun CalendarScreen(onBackPressed: () -> Unit) {
val calendarViewModel: CalendarViewModel = viewModel()

CalendarContent(
selectedDates = calendarViewModel.datesSelected.toString(),
onDayClicked = { calendarDay, calendarMonth ->
calendarViewModel.onDaySelected(DaySelected(calendarDay.value.toInt(), calendarMonth))
},
onBackPressed = onBackPressed
)
}

@Composable
fun CalendarScreen(onBackPressed: () -> Unit) {
private fun CalendarContent(
selectedDates: String,
onDayClicked: (CalendarDay, CalendarMonth) -> Unit,
onBackPressed: () -> Unit
) {
CraneScaffold {
Column {
val selectedDatesText = ServiceLocator.datesSelected.toString()
TopAppBar(
title = {
Text(
text = if (selectedDatesText.isEmpty()) "Select Dates"
else selectedDatesText
text = if (selectedDates.isEmpty()) "Select Dates"
else selectedDates
)
},
navigationIcon = {
Expand Down
Loading