Skip to content
Permalink
Browse files
Include codelab as search result
Bug: 130516731

Change-Id: I8472a8fb62b16b311a7095e8ddf4430679251920
  • Loading branch information
thagikura committed Aug 14, 2019
1 parent 3651746 commit 1a1237b7366b5f717cf1914d82a34e16cf95c951
@@ -36,6 +36,7 @@ import com.google.samples.apps.iosched.ui.MainNavigationFragment
import com.google.samples.apps.iosched.ui.search.SearchFragmentDirections.Companion.toSessionDetail
import com.google.samples.apps.iosched.ui.search.SearchFragmentDirections.Companion.toSpeakerDetail
import com.google.samples.apps.iosched.util.doOnApplyWindowInsets
import com.google.samples.apps.iosched.util.openWebsiteUrl
import kotlinx.android.synthetic.main.fragment_search.view.searchView
import javax.inject.Inject

@@ -69,6 +70,9 @@ class SearchFragment : MainNavigationFragment() {
viewModel.navigateToSpeakerAction.observe(this, EventObserver { speakerId ->
findNavController().navigate(toSpeakerDetail(speakerId))
})
viewModel.navigateToCodelabAction.observe(this, EventObserver { url ->
openWebsiteUrl(requireActivity(), url)
})
analyticsHelper.sendScreenView("Search", requireActivity())
}

@@ -27,6 +27,7 @@ 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.domain.search.Searchable
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedCodelab
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedSession
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedSpeaker
import com.google.samples.apps.iosched.shared.result.Event
@@ -56,6 +57,10 @@ class SearchViewModel @Inject constructor(
private val _navigateToSpeakerAction = MutableLiveData<Event<SpeakerId>>()
val navigateToSpeakerAction: LiveData<Event<SpeakerId>> = _navigateToSpeakerAction

// Has codelabUrl as String
private val _navigateToCodelabAction = MutableLiveData<Event<String>>()
val navigateToCodelabAction: LiveData<Event<String>> = _navigateToCodelabAction

private val loadSearchResults = MutableLiveData<Result<List<Searchable>>>()
val searchResults: LiveData<List<SearchResult>>

@@ -71,7 +76,7 @@ class SearchViewModel @Inject constructor(
SearchResult(
session.title,
session.type.displayName,
SearchResultType.SESSION,
SESSION,
session.id
)
}
@@ -80,10 +85,21 @@ class SearchViewModel @Inject constructor(
SearchResult(
speaker.name,
"Speaker",
SearchResultType.SPEAKER,
SPEAKER,
speaker.id
)
}
is SearchedCodelab -> {
val codelab = searched.codelab
SearchResult(
codelab.title,
"Codelab",
CODELAB,
// This may not be unique, but to navigate to the meaningful page,
// assigning codelabUrl as id
codelab.codelabUrl
)
}
}
}
}
@@ -108,7 +124,10 @@ class SearchViewModel @Inject constructor(
_navigateToSpeakerAction.value = Event(speakerId)
}
CODELAB -> {
// not implemented
val url = searchResult.objectId
analyticsHelper.logUiEvent("Codelab: $url",
AnalyticsActions.SEARCH_RESULT_CLICK)
_navigateToCodelabAction.value = Event(url)
}
}
}
@@ -20,6 +20,7 @@ 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.CodelabFtsDao
import com.google.samples.apps.iosched.shared.data.db.SessionFtsDao
import com.google.samples.apps.iosched.shared.data.db.SpeakerFtsDao
import org.mockito.Mockito
@@ -33,6 +34,10 @@ class FakeAppDatabase : AppDatabase() {
return Mockito.mock(SpeakerFtsDao::class.java)
}

override fun codelabFtsDao(): CodelabFtsDao {
return Mockito.mock(CodelabFtsDao::class.java)
}

override fun createOpenHelper(config: DatabaseConfiguration?): SupportSQLiteOpenHelper {
return Mockito.mock(SupportSQLiteOpenHelper::class.java)
}
@@ -21,6 +21,7 @@ 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.CodelabFtsEntity
import com.google.samples.apps.iosched.shared.data.db.SessionFtsEntity
import com.google.samples.apps.iosched.shared.data.db.SpeakerFtsEntity
import com.google.samples.apps.iosched.shared.util.TimeUtils
@@ -136,6 +137,14 @@ open class ConferenceDataRepository @Inject constructor(
)
}
appDatabase.speakerFtsDao().insertAll(speakers)
val codelabs = conferenceData.codelabs.map {
CodelabFtsEntity(
codelabId = it.id,
title = it.title,
description = it.description
)
}
appDatabase.codelabFtsDao().insertAll(codelabs)
}

open fun getConferenceDays(): List<ConferenceDay> = TimeUtils.ConferenceDays
@@ -26,13 +26,15 @@ import androidx.room.RoomDatabase
*/
@Database(entities = [
SessionFtsEntity::class,
SpeakerFtsEntity::class
SpeakerFtsEntity::class,
CodelabFtsEntity::class
],
version = 2,
version = 3,
exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun sessionFtsDao(): SessionFtsDao
abstract fun speakerFtsDao(): SpeakerFtsDao
abstract fun codelabFtsDao(): CodelabFtsDao

companion object {
private const val databaseName = "iosched-db"
@@ -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 [CodelabFtsEntity] class.
*/
@Dao
interface CodelabFtsDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(codelabs: List<CodelabFtsEntity>)

@Query("SELECT codelabId FROM codelabsFts WHERE codelabsFts MATCH :query")
fun searchAllCodelabs(query: String): List<String>
}
@@ -0,0 +1,57 @@
/*
* 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.ColumnInfo
import androidx.room.Entity
import androidx.room.Fts4

/**
* This class represents Codelab data for searching with FTS.
*
* The [ColumnInfo] name is explicitly declared to allow flexibility for renaming the data class
* properties without requiring changing the column name.
*/
@Entity(tableName = "codelabsFts")
@Fts4
data class CodelabFtsEntity(

/**
* An FTS entity table always has a column named rowid that is the equivalent of an
* INTEGER PRIMARY KEY index. Therefore, an FTS entity can only have a single field
* annotated with PrimaryKey, it must be named rowid and must be of INTEGER affinity.
*
* The field can be optionally omitted in the class (as is done here),
* but can still be used in queries.
*/

@ColumnInfo(name = "codelabId")
val codelabId: String,

@ColumnInfo(name = "title")
val title: String,

/**
* Body of text with the speaker's description.
*
* Note that this is named "abstract" in Speaker. This causes an issue with kapt, which
* in turn causes issues with Room. For more details, see
* https://youtrack.jetbrains.com/issue/KT-27804.
*/
@ColumnInfo(name = "description")
val description: String
)
@@ -20,6 +20,7 @@ import com.google.samples.apps.iosched.shared.data.ConferenceDataRepository
import com.google.samples.apps.iosched.shared.data.db.AppDatabase
import com.google.samples.apps.iosched.shared.data.session.SessionRepository
import com.google.samples.apps.iosched.shared.domain.UseCase
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedCodelab
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedSession
import com.google.samples.apps.iosched.shared.domain.search.Searchable.SearchedSpeaker
import javax.inject.Inject
@@ -37,14 +38,19 @@ class SearchDbUseCase @Inject constructor(
val query = parameters.toLowerCase()
val sessionResults = appDatabase.sessionFtsDao().searchAllSessions(query).toSet()
val speakerResults = appDatabase.speakerFtsDao().searchAllSpeakers(query).toSet()
val codelabResults = appDatabase.codelabFtsDao().searchAllCodelabs(query).toSet()
val searchedSessions = sessionRepository.getSessions()
.filter { session -> session.id in sessionResults }
// Keynotes come first, followed by sessions, app reviews, game reviews ...
.sortedBy { it.type }
.map { SearchedSession(it) }
val searchedSpeakers = conferenceRepository.getOfflineConferenceData().speakers.filter {
val conferenceData = conferenceRepository.getOfflineConferenceData()
val searchedSpeakers = conferenceData.speakers.filter {
it.id in speakerResults
}.map { SearchedSpeaker(it) }
return searchedSessions.plus(searchedSpeakers)
val searchedCodelabs = conferenceData.codelabs.filter {
it.id in codelabResults
}.map { SearchedCodelab(it) }
return searchedSessions.plus(searchedSpeakers).plus(searchedCodelabs)
}
}
@@ -16,6 +16,7 @@

package com.google.samples.apps.iosched.shared.domain.search

import com.google.samples.apps.iosched.model.Codelab
import com.google.samples.apps.iosched.model.Session
import com.google.samples.apps.iosched.model.Speaker

@@ -26,4 +27,5 @@ sealed class Searchable {

class SearchedSession(val session: Session) : Searchable()
class SearchedSpeaker(val speaker: Speaker) : Searchable()
class SearchedCodelab(val codelab: Codelab) : Searchable()
}
@@ -20,6 +20,7 @@ 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.CodelabFtsDao
import com.google.samples.apps.iosched.shared.data.db.SessionFtsDao
import com.google.samples.apps.iosched.shared.data.db.SpeakerFtsDao
import org.mockito.Mockito
@@ -33,6 +34,10 @@ class FakeAppDatabase : AppDatabase() {
return Mockito.mock(SpeakerFtsDao::class.java)
}

override fun codelabFtsDao(): CodelabFtsDao {
return Mockito.mock(CodelabFtsDao::class.java)
}

override fun createOpenHelper(config: DatabaseConfiguration?): SupportSQLiteOpenHelper {
return Mockito.mock(SupportSQLiteOpenHelper::class.java)
}
@@ -20,6 +20,7 @@ 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.CodelabFtsDao
import com.google.samples.apps.iosched.shared.data.db.SessionFtsDao
import com.google.samples.apps.iosched.shared.data.db.SpeakerFtsDao
import org.mockito.Mockito
@@ -34,6 +35,10 @@ class FakeAppDatabase : AppDatabase() {
return Mockito.mock(SpeakerFtsDao::class.java)
}

override fun codelabFtsDao(): CodelabFtsDao {
return Mockito.mock(CodelabFtsDao::class.java)
}

override fun createOpenHelper(config: DatabaseConfiguration?): SupportSQLiteOpenHelper {
return Mockito.mock(SupportSQLiteOpenHelper::class.java)
}
@@ -43,4 +48,4 @@ class FakeAppDatabase : AppDatabase() {
}

override fun clearAllTables() {}
}
}

0 comments on commit 1a1237b

Please sign in to comment.