Skip to content

Commit 33ce614

Browse files
authored
feat(android): add getting-started example (#498)
1 parent be33928 commit 33ce614

42 files changed

Lines changed: 1343 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.iml
2+
/.gradle
3+
/local.properties
4+
/.idea
5+
/.kotlin
6+
.DS_Store
7+
/build
8+
/captures
9+
.externalNativeBuild
10+
.cxx
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# InstantSearch Android getting started sample
2+
3+
This sample shows how to get started on building your search experience by easily adding common InstantSearch components.
4+
5+
## Get started
6+
7+
Clone the getting started sample:
8+
9+
```sh
10+
curl https://codeload.github.com/algolia/doc-code-samples/tar.gz/master | tar -xz --strip=2 doc-code-samples-master/instantsearch-android/getting-started
11+
```
12+
13+
Build this project:
14+
15+
```sh
16+
./gradlew assembleDebug
17+
```
18+
19+
Install it on a connected device:
20+
21+
```sh
22+
./gradlew installDebug
23+
```
24+
25+
## Additional resources
26+
Learn more about InstantSearch Android [widgets](https://www.algolia.com/doc/guides/building-search-ui/widgets/showcase/android/) in the Algolia documentation.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.kotlin.android)
4+
alias(libs.plugins.kotlin.compose)
5+
kotlin("plugin.serialization") version "2.0.0"
6+
}
7+
8+
android {
9+
namespace = "com.example.searchApp"
10+
compileSdk = 34
11+
12+
defaultConfig {
13+
applicationId = "com.example.searchApp"
14+
minSdk = 24
15+
targetSdk = 34
16+
versionCode = 1
17+
versionName = "1.0"
18+
19+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20+
}
21+
22+
buildTypes {
23+
release {
24+
isMinifyEnabled = false
25+
proguardFiles(
26+
getDefaultProguardFile("proguard-android-optimize.txt"),
27+
"proguard-rules.pro"
28+
)
29+
}
30+
}
31+
compileOptions {
32+
sourceCompatibility = JavaVersion.VERSION_11
33+
targetCompatibility = JavaVersion.VERSION_11
34+
}
35+
kotlinOptions {
36+
jvmTarget = "11"
37+
}
38+
buildFeatures {
39+
compose = true
40+
}
41+
}
42+
43+
dependencies {
44+
implementation(libs.kotlinx.serialization.json)
45+
implementation(libs.androidx.core.ktx)
46+
implementation(libs.androidx.lifecycle.viewmodel.ktx)
47+
implementation(libs.androidx.lifecycle.livedata.ktx)
48+
implementation(libs.androidx.lifecycle.runtime.ktx)
49+
implementation(libs.androidx.activity.ktx)
50+
implementation(libs.androidx.activity.compose)
51+
implementation(libs.androidx.paging.compose)
52+
implementation(platform(libs.androidx.compose.bom))
53+
implementation(libs.androidx.ui)
54+
implementation(libs.androidx.ui.graphics)
55+
implementation(libs.androidx.ui.tooling.preview)
56+
implementation(libs.androidx.material3)
57+
implementation(libs.androidx.material.icons.extended)
58+
testImplementation(libs.junit)
59+
androidTestImplementation(libs.androidx.junit)
60+
androidTestImplementation(libs.androidx.espresso.core)
61+
androidTestImplementation(platform(libs.androidx.compose.bom))
62+
androidTestImplementation(libs.androidx.ui.test.junit4)
63+
debugImplementation(libs.androidx.ui.tooling)
64+
debugImplementation(libs.androidx.ui.test.manifest)
65+
66+
implementation(libs.instantsearch.compose)
67+
implementation(libs.instantsearch.android)
68+
implementation(libs.instantsearch.android.paging3)
69+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.example.searchApp
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
9+
import org.junit.Assert.*
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* See [testing documentation](http://d.android.com/tools/testing).
15+
*/
16+
@RunWith(AndroidJUnit4::class)
17+
class ExampleInstrumentedTest {
18+
@Test
19+
fun useAppContext() {
20+
// Context of the app under test.
21+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22+
assertEquals("com.example.searchApp", appContext.packageName)
23+
}
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
<application
6+
android:allowBackup="true"
7+
android:dataExtractionRules="@xml/data_extraction_rules"
8+
android:fullBackupContent="@xml/backup_rules"
9+
android:icon="@mipmap/ic_launcher"
10+
android:label="@string/app_name"
11+
android:roundIcon="@mipmap/ic_launcher_round"
12+
android:supportsRtl="true"
13+
android:theme="@style/Theme.SearchApp"
14+
tools:targetApi="31">
15+
<activity
16+
android:name="com.example.searchApp.MainActivity"
17+
android:exported="true"
18+
android:theme="@style/Theme.SearchApp">
19+
<intent-filter>
20+
<action android:name="android.intent.action.MAIN" />
21+
22+
<category android:name="android.intent.category.LAUNCHER" />
23+
</intent-filter>
24+
</activity>
25+
</application>
26+
27+
</manifest>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.example.searchApp
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.activity.enableEdgeToEdge
7+
import androidx.activity.viewModels
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material3.Scaffold
11+
import androidx.compose.ui.Modifier
12+
import com.example.searchApp.ui.theme.SearchAppTheme
13+
14+
class MainActivity : ComponentActivity() {
15+
private val viewModel: MainViewModel by viewModels()
16+
17+
override fun onCreate(savedInstanceState: Bundle?) {
18+
super.onCreate(savedInstanceState)
19+
enableEdgeToEdge()
20+
setContent {
21+
SearchAppTheme {
22+
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
23+
Search(
24+
modifier = Modifier.padding(innerPadding),
25+
searchBoxState = viewModel.searchBoxState,
26+
paginator = viewModel.hitsPaginator,
27+
statsText = viewModel.statsText,
28+
facetList = viewModel.facetList,
29+
)
30+
}
31+
}
32+
}
33+
}
34+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.example.searchApp
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.paging.PagingConfig
5+
import com.algolia.instantsearch.android.paging3.Paginator
6+
import com.algolia.instantsearch.android.paging3.facet.connectPaginator
7+
import com.algolia.instantsearch.android.paging3.searchbox.connectPaginator
8+
import com.algolia.instantsearch.compose.filter.facet.FacetListState
9+
import com.algolia.instantsearch.compose.item.StatsTextState
10+
import com.algolia.instantsearch.compose.searchbox.SearchBoxState
11+
import com.algolia.instantsearch.core.connection.ConnectionHandler
12+
import com.algolia.instantsearch.core.selectable.list.SelectionMode
13+
import com.algolia.instantsearch.filter.facet.FacetListConnector
14+
import com.algolia.instantsearch.filter.facet.connectView
15+
import com.algolia.instantsearch.filter.state.FilterState
16+
import com.algolia.instantsearch.searchbox.SearchBoxConnector
17+
import com.algolia.instantsearch.searchbox.connectView
18+
import com.algolia.instantsearch.searcher.connectFilterState
19+
import com.algolia.instantsearch.searcher.facets.FacetsSearcher
20+
import com.algolia.instantsearch.searcher.hits.HitsSearcher
21+
22+
import com.algolia.instantsearch.stats.DefaultStatsPresenter
23+
import com.algolia.instantsearch.stats.StatsConnector
24+
import com.algolia.instantsearch.stats.connectView
25+
import com.algolia.search.client.ClientSearch
26+
import com.algolia.search.logging.LogLevel
27+
import com.algolia.search.model.APIKey
28+
import com.algolia.search.model.ApplicationID
29+
import com.algolia.search.model.Attribute
30+
import com.algolia.search.model.IndexName
31+
32+
class MainViewModel: ViewModel() {
33+
private val client = ClientSearch(
34+
ApplicationID("latency"),
35+
APIKey("1f6fd3a6fb973cb08419fe7d288fa4db"),
36+
LogLevel.All
37+
)
38+
private val indexName = IndexName("instant_search")
39+
private val searcher = HitsSearcher(client, indexName)
40+
41+
// Search Box
42+
val searchBoxState = SearchBoxState()
43+
private val searchBoxConnector = SearchBoxConnector(searcher)
44+
45+
// Hits
46+
val hitsPaginator = Paginator(
47+
searcher,
48+
PagingConfig(pageSize = 50, initialLoadSize = 50)
49+
) { it.deserialize(Product.serializer()) }
50+
51+
// Stats
52+
val statsText = StatsTextState()
53+
private val statsConnector = StatsConnector(searcher)
54+
55+
// Filters
56+
val facetList = FacetListState()
57+
private val filterState = FilterState()
58+
private val categories = Attribute("categories")
59+
private val searcherForFacet = FacetsSearcher(client, indexName, categories)
60+
private val facetListConnector = FacetListConnector(
61+
searcher = searcherForFacet,
62+
filterState = filterState,
63+
attribute = categories,
64+
selectionMode = SelectionMode.Multiple
65+
)
66+
67+
private val connections = ConnectionHandler(searchBoxConnector, statsConnector, facetListConnector)
68+
69+
init {
70+
connections += searchBoxConnector.connectView(searchBoxState)
71+
connections += statsConnector.connectView(statsText, DefaultStatsPresenter())
72+
connections += searcher.connectFilterState(filterState)
73+
connections += facetListConnector.connectView(facetList)
74+
connections += facetListConnector.connectPaginator(hitsPaginator)
75+
connections += searchBoxConnector.connectPaginator(hitsPaginator)
76+
77+
searcherForFacet.query.maxFacetHits = 100
78+
searcherForFacet.searchAsync()
79+
}
80+
81+
override fun onCleared() {
82+
super.onCleared()
83+
searcher.cancel()
84+
connections.clear()
85+
searcherForFacet.cancel()
86+
}
87+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.example.searchApp
2+
3+
import com.algolia.instantsearch.core.highlighting.HighlightedString
4+
import com.algolia.instantsearch.highlighting.Highlightable
5+
import com.algolia.search.model.Attribute
6+
import com.algolia.search.model.ObjectID
7+
import com.algolia.search.model.indexing.Indexable
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.json.JsonObject
10+
11+
@Serializable
12+
data class Product(
13+
val name: String,
14+
override val objectID: ObjectID,
15+
override val _highlightResult: JsonObject?
16+
) : Indexable, Highlightable {
17+
val highlightedName: HighlightedString?
18+
get() = getHighlight(Attribute("name"))
19+
}

0 commit comments

Comments
 (0)