Skip to content
Permalink
Browse files

Use Room for search

Bug: 130516615
Test: kokoro
Change-Id: I617e3bb6ccb30db137b4d9402dc014d0b1f037d5
  • Loading branch information...
tiembo authored and thagikura committed Mar 27, 2019
1 parent e0fef97 commit e51008748d4a6c2ec6e2010f6348e9d1e138e717
Showing with 433 additions and 19 deletions.
  1. +1 −1 build.gradle
  2. +3 −0 mobile/build.gradle
  3. +5 −0 mobile/src/main/java/com/google/samples/apps/iosched/di/AppModule.kt
  4. +16 −3 mobile/src/main/java/com/google/samples/apps/iosched/ui/search/SearchViewModel.kt
  5. +6 −1 mobile/src/test/java/com/google/samples/apps/iosched/model/MobileTestData.kt
  6. +40 −0 mobile/src/test/java/com/google/samples/apps/iosched/test/util/fakes/FakeAppDatabase.kt
  7. +5 −2 mobile/src/test/java/com/google/samples/apps/iosched/ui/schedule/ScheduleViewModelTest.kt
  8. +3 −1 shared/build.gradle
  9. +4 −2 shared/src/debugRelease/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt
  10. +25 −2 shared/src/main/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepository.kt
  11. +1 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/config/AppConfigDataSource.kt
  12. +4 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/config/RemoteAppConfigDataSource.kt
  13. +38 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/db/AppDatabase.kt
  14. +34 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/db/SessionFtsDao.kt
  15. +70 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/data/db/SessionFtsEntity.kt
  16. +8 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/di/FeatureFlagAnnotations.kt
  17. +6 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/di/FeatureFlagsModule.kt
  18. +38 −0 shared/src/main/java/com/google/samples/apps/iosched/shared/domain/search/SearchDbUseCase.kt
  19. +4 −0 shared/src/main/res/xml/remote_config_defaults.xml
  20. +1 −0 shared/src/staging/java/com/google/samples/apps/iosched/shared/data/FakeAppConfigDataSource.kt
  21. +4 −2 shared/src/staging/java/com/google/samples/apps/iosched/shared/di/SharedModule.kt
  22. +7 −3 shared/src/test/java/com/google/samples/apps/iosched/shared/data/ConferenceDataRepositoryTest.kt
  23. +6 −1 shared/src/test/java/com/google/samples/apps/iosched/shared/model/SharedTestData.kt
  24. +40 −0 shared/src/test/java/com/google/samples/apps/iosched/test/util/FakeAppDatabase.kt
  25. +3 −0 tv/build.gradle
  26. +6 −0 tv/src/main/java/com/google/samples/apps/iosched/tv/di/TvAppModule.kt
  27. +6 −1 tv/src/test/java/com/google/samples/apps/iosched/tv/model/TvTestData.kt
  28. +3 −0 tv/src/test/java/com/google/samples/apps/iosched/tv/ui/schedule/ScheduleViewModelTest.kt
  29. +2 −0 tv/src/test/java/com/google/samples/apps/iosched/tv/ui/search/SearchableViewModelTest.kt
  30. +2 −0 tv/src/test/java/com/google/samples/apps/iosched/tv/ui/sessiondetail/SessionDetailViewModelTest.kt
  31. +2 −0 tv/src/test/java/com/google/samples/apps/iosched/tv/ui/sessionplayer/SessionPlayerModelTest.kt
  32. +40 −0 tv/src/test/java/com/google/samples/apps/iosched/tv/util/FakeAppDatabase.kt
@@ -73,7 +73,7 @@ buildscript {
okhttpVersion = "3.10.0"
okioVersion = '1.14.0'
pageIndicatorVersion = "1.3.0"
roomVersion = '2.1.0-alpha05'
roomVersion = '2.1.0-alpha06'
rulesVersion = '1.1.1'
runnerVersion = '1.1.1'
testExtVersion = '1.1.0'
@@ -148,6 +148,9 @@ dependencies {
testImplementation "androidx.arch.core:core-testing:$rootProject.archTestingVersion"
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
implementation "androidx.room:room-ktx:$rootProject.roomVersion"
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"

// Dagger
implementation "com.google.dagger:dagger-android:$rootProject.dagger"
@@ -29,6 +29,7 @@ import com.google.samples.apps.iosched.shared.data.prefs.SharedPreferenceStorage
import com.google.samples.apps.iosched.shared.data.agenda.AgendaRepository
import com.google.samples.apps.iosched.shared.data.agenda.DefaultAgendaRepository
import com.google.samples.apps.iosched.shared.data.config.AppConfigDataSource
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
import com.google.samples.apps.iosched.shared.di.MainThreadHandler
import com.google.samples.apps.iosched.ui.signin.SignInViewModelDelegate
import com.google.samples.apps.iosched.util.FirebaseAnalyticsHelper
@@ -86,4 +87,8 @@ class AppModule {
@Provides
fun provideAgendaRepository(appConfigDataSource: AppConfigDataSource): AgendaRepository =
DefaultAgendaRepository(appConfigDataSource)

@Singleton
@Provides
fun providesAppDatabase(context: Context): AppDatabase = AppDatabase.buildDatabase(context)
}
@@ -23,6 +23,8 @@ import com.google.samples.apps.iosched.model.Session
import com.google.samples.apps.iosched.model.SessionId
import com.google.samples.apps.iosched.shared.analytics.AnalyticsActions
import com.google.samples.apps.iosched.shared.analytics.AnalyticsHelper
import com.google.samples.apps.iosched.shared.di.SearchUsingRoomEnabledFlag
import com.google.samples.apps.iosched.shared.domain.search.SearchDbUseCase
import com.google.samples.apps.iosched.shared.domain.search.SearchUseCase
import com.google.samples.apps.iosched.shared.result.Event
import com.google.samples.apps.iosched.shared.result.Result
@@ -33,9 +35,15 @@ import javax.inject.Inject

class SearchViewModel @Inject constructor(
private val analyticsHelper: AnalyticsHelper,
private val loadSearchResultsUseCase: SearchUseCase
private val loadSearchResultsUseCase: SearchUseCase,
private val loadDbSearchResultsUseCase: SearchDbUseCase
) : ViewModel(), SearchResultActionHandler {

@Inject
@JvmField
@SearchUsingRoomEnabledFlag
var searchUsingRoomFeatureEnabled: Boolean = false

private val _navigateToSessionAction = MutableLiveData<Event<SessionId>>()
val navigateToSessionAction: LiveData<Event<SessionId>>
get() = _navigateToSessionAction
@@ -67,8 +75,13 @@ class SearchViewModel @Inject constructor(
}

fun onScheduleSearchQuerySubmitted(query: String) {
Timber.d("Searching for query: $query")
analyticsHelper.logUiEvent("Query: $query", AnalyticsActions.SEARCH_QUERY_SUBMIT)
loadSearchResultsUseCase(query, loadSearchResults)
if (searchUsingRoomFeatureEnabled) {
Timber.d("Searching for query using Room: $query")
loadDbSearchResultsUseCase(query, loadSearchResults)
} else {
Timber.d("Searching for query without using Room: $query")
loadSearchResultsUseCase(query, loadSearchResults)
}
}
}
@@ -23,6 +23,7 @@ import com.google.samples.apps.iosched.test.data.TestData.androidTag
import com.google.samples.apps.iosched.test.data.TestData.cloudTag
import com.google.samples.apps.iosched.test.data.TestData.sessionsTag
import com.google.samples.apps.iosched.test.data.TestData.webTag
import com.google.samples.apps.iosched.test.util.fakes.FakeAppDatabase
import com.google.samples.apps.iosched.ui.schedule.filters.EventFilter.TagFilter

/**
@@ -47,7 +48,11 @@ object TestDataSource : ConferenceDataSource {
}

/** ConferenceDataRepository for tests */
object TestDataRepository : ConferenceDataRepository(TestDataSource, TestDataSource) {
object TestDataRepository : ConferenceDataRepository(
TestDataSource,
TestDataSource,
FakeAppDatabase()
) {
override fun getConferenceDays(): List<ConferenceDay> {
return TestData.TestConferenceDays
}
@@ -0,0 +1,40 @@
/*
* Copyright 2019 Google LLC
*
* 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 com.google.samples.apps.iosched.test.util.fakes

import androidx.room.DatabaseConfiguration
import androidx.room.InvalidationTracker
import androidx.sqlite.db.SupportSQLiteOpenHelper
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
import com.google.samples.apps.iosched.shared.data.db.SessionFtsDao
import org.mockito.Mockito

class FakeAppDatabase : AppDatabase() {
override fun sessionFtsDao(): SessionFtsDao {
return Mockito.mock(SessionFtsDao::class.java)
}

override fun createOpenHelper(config: DatabaseConfiguration?): SupportSQLiteOpenHelper {
return Mockito.mock(SupportSQLiteOpenHelper::class.java)
}

override fun createInvalidationTracker(): InvalidationTracker {
return Mockito.mock(InvalidationTracker::class.java)
}

override fun clearAllTables() {}
}
@@ -56,6 +56,7 @@ import com.google.samples.apps.iosched.shared.schedule.UserSessionMatcher
import com.google.samples.apps.iosched.test.data.TestData
import com.google.samples.apps.iosched.test.util.SyncTaskExecutorRule
import com.google.samples.apps.iosched.test.util.fakes.FakeAnalyticsHelper
import com.google.samples.apps.iosched.test.util.fakes.FakeAppDatabase
import com.google.samples.apps.iosched.test.util.fakes.FakePreferenceStorage
import com.google.samples.apps.iosched.test.util.fakes.FakeSignInViewModelDelegate
import com.google.samples.apps.iosched.test.util.fakes.FakeStarEventUseCase
@@ -361,7 +362,8 @@ class ScheduleViewModelTest {
refreshConferenceDataUseCase = RefreshConferenceDataUseCase(
ConferenceDataRepository(
remoteDataSource = remoteDataSource,
boostrapDataSource = TestDataSource
boostrapDataSource = TestDataSource,
appDatabase = FakeAppDatabase()
)
)
)
@@ -380,7 +382,8 @@ class ScheduleViewModelTest {
fun newDataFromConfRepo_scheduleUpdated() {
val repo = ConferenceDataRepository(
remoteDataSource = TestConfDataSourceSession0(),
boostrapDataSource = BootstrapDataSourceSession3()
boostrapDataSource = BootstrapDataSourceSession3(),
appDatabase = FakeAppDatabase()
)

val loadUserSessionsByDayUseCase = createTestLoadUserSessionsByDayUseCase(
@@ -91,7 +91,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-livedata:$rootProject.lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel:$rootProject.lifecycleVersion"

implementation "androidx.room:room-ktx:$rootProject.roomVersion"
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"

// Maps
api("com.google.maps.android:android-maps-utils:$rootProject.googleMapUtilsVersion") {
@@ -32,6 +32,7 @@ import com.google.samples.apps.iosched.shared.data.ar.ArDebugFlagEndpoint
import com.google.samples.apps.iosched.shared.data.ar.DefaultArDebugFlagEndpoint
import com.google.samples.apps.iosched.shared.data.config.AppConfigDataSource
import com.google.samples.apps.iosched.shared.data.config.RemoteAppConfigDataSource
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
import com.google.samples.apps.iosched.shared.data.feed.AnnouncementDataSource
import com.google.samples.apps.iosched.shared.data.feed.DefaultFeedRepository
import com.google.samples.apps.iosched.shared.data.feed.FeedRepository
@@ -85,9 +86,10 @@ class SharedModule {
@Provides
fun provideConferenceDataRepository(
@Named("remoteConfDatasource") remoteDataSource: ConferenceDataSource,
@Named("bootstrapConfDataSource") boostrapDataSource: ConferenceDataSource
@Named("bootstrapConfDataSource") boostrapDataSource: ConferenceDataSource,
appDatabase: AppDatabase
): ConferenceDataRepository {
return ConferenceDataRepository(remoteDataSource, boostrapDataSource)
return ConferenceDataRepository(remoteDataSource, boostrapDataSource, appDatabase)
}

@Singleton
@@ -20,6 +20,8 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.google.samples.apps.iosched.model.ConferenceData
import com.google.samples.apps.iosched.model.ConferenceDay
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
import com.google.samples.apps.iosched.shared.data.db.SessionFtsEntity
import com.google.samples.apps.iosched.shared.util.TimeUtils
import java.io.IOException
import javax.inject.Inject
@@ -34,7 +36,8 @@ import javax.inject.Singleton
@Singleton
open class ConferenceDataRepository @Inject constructor(
@Named("remoteConfDatasource") private val remoteDataSource: ConferenceDataSource,
@Named("bootstrapConfDataSource") private val boostrapDataSource: ConferenceDataSource
@Named("bootstrapConfDataSource") private val boostrapDataSource: ConferenceDataSource,
private val appDatabase: AppDatabase
) {

// In-memory cache of the conference data
@@ -74,6 +77,7 @@ open class ConferenceDataRepository @Inject constructor(
// Update cache
synchronized(loadConfDataLock) {
conferenceDataCache = conferenceData
populateSearchData(conferenceData)
}

// Update meta
@@ -85,12 +89,18 @@ open class ConferenceDataRepository @Inject constructor(

fun getOfflineConferenceData(): ConferenceData {
synchronized(loadConfDataLock) {
val offlineData = conferenceDataCache ?: getCacheOrBootstrapData()
val offlineData = conferenceDataCache ?: getCacheOrBootstrapDataAndPopulateSearch()
conferenceDataCache = offlineData
return offlineData
}
}

private fun getCacheOrBootstrapDataAndPopulateSearch(): ConferenceData {
val conferenceData = getCacheOrBootstrapData()
populateSearchData(conferenceData)
return conferenceData
}

private fun getCacheOrBootstrapData(): ConferenceData {
// First, try the local cache:
var conferenceData = remoteDataSource.getOfflineConferenceData()
@@ -107,5 +117,18 @@ open class ConferenceDataRepository @Inject constructor(
return conferenceData
}

open fun populateSearchData(conferenceData: ConferenceData) {
val sessionFtsEntities = mutableListOf<SessionFtsEntity>()
conferenceData.sessions.forEach { session ->
sessionFtsEntities.add(SessionFtsEntity(
sessionId = session.id,
title = session.title,
description = session.abstract,
speakers = session.speakers.joinToString { it.name }
))
}
appDatabase.sessionFtsDao().insertAll(sessionFtsEntities)
}

open fun getConferenceDays(): List<ConferenceDay> = TimeUtils.ConferenceDays
}
@@ -28,6 +28,7 @@ interface AppConfigDataSource {
fun isExploreArFeatureEnabled(): Boolean
fun isCodelabsFeatureEnabled(): Boolean
fun isSearchScheduleFeatureEnabled(): Boolean
fun isSearchUsingRoomFeatureEnabled(): Boolean
fun isAssistantAppFeatureEnabled(): Boolean
}

@@ -173,6 +173,9 @@ class RemoteAppConfigDataSource @Inject constructor(
override fun isSearchScheduleFeatureEnabled(): Boolean =
firebaseRemoteConfig.getBoolean(SEARCH_SCHEDULE_FEATURE_ENABLED)

override fun isSearchUsingRoomFeatureEnabled(): Boolean =
firebaseRemoteConfig.getBoolean(SEARCH_USING_ROOM_FEATURE_ENABLED)

override fun isAssistantAppFeatureEnabled(): Boolean =
firebaseRemoteConfig.getBoolean(ASSISTANT_APP_FEATURE_ENABLED)

@@ -242,6 +245,7 @@ class RemoteAppConfigDataSource @Inject constructor(
const val EXPLORE_AR_FEATURE_ENABLED = "explore_ar_enabled"
const val CODELABS_FEATURE_ENABLED = "codelabs_enabled"
const val SEARCH_SCHEDULE_FEATURE_ENABLED = "search_schedule_enabled"
const val SEARCH_USING_ROOM_FEATURE_ENABLED = "search_using_room_enabled"
const val ASSISTANT_APP_FEATURE_ENABLED = "io_assistant_app_enabled"

val DEFAULT_CACHE_EXPIRY_S = TimeUnit.MINUTES.toSeconds(12)
@@ -0,0 +1,38 @@
/*
* Copyright 2019 Google LLC
*
* 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 com.google.samples.apps.iosched.shared.data.db

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

/**
* The [Room] database for this app.
*/
@Database(entities = [SessionFtsEntity::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun sessionFtsDao(): SessionFtsDao

companion object {
private const val databaseName = "iosched-db"

fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, databaseName).build()
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2019 Google LLC
*
* 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 com.google.samples.apps.iosched.shared.data.db

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

/**
* The Data Access Object for the [SessionFtsEntity] class.
*/
@Dao
interface SessionFtsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(sessions: List<SessionFtsEntity>)

@Query("SELECT sessionId FROM sessionsFts WHERE sessionsFts MATCH :query")
fun searchAllSessions(query: String): List<String>
}

0 comments on commit e510087

Please sign in to comment.
You can’t perform that action at this time.