Skip to content

Commit

Permalink
feat: Adding Flow support for getting place selection events.
Browse files Browse the repository at this point in the history
Change-Id: I51108f10b4576a016dd258d2101e8f64a0af02bd
  • Loading branch information
arriolac committed Jul 10, 2020
1 parent 6363c57 commit 422c199
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 26 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ android {
}

kotlinOptions {
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
Expand Down
13 changes: 8 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,18 @@
android:supportsRtl="true"
android:name=".DemoApplication"
android:theme="@style/AppTheme">
<activity
android:name=".PlacesSearchDemoActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" >
<activity android:name=".DemoActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity android:name=".AutocompleteDemoActivity" />

<activity
android:name=".PlacesSearchDemoActivity"
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.google.places.android.ktx.demo

import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.ktx.widget.PlaceSelectionError
import com.google.android.libraries.places.ktx.widget.PlaceSelectionSuccess
import com.google.android.libraries.places.ktx.widget.placeSelectionEvents
import com.google.android.libraries.places.widget.AutocompleteSupportFragment
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect

class AutocompleteDemoActivity : AppCompatActivity() {

@OptIn(ExperimentalCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_autocomplete)

val autocompleteFragment =
supportFragmentManager.findFragmentById(R.id.autocomplete_fragment)
as AutocompleteSupportFragment

// Specify the types of place data to return.
autocompleteFragment.setPlaceFields(listOf(Place.Field.ID, Place.Field.NAME))

// Listen to place selection events
lifecycleScope.launchWhenCreated {
autocompleteFragment.placeSelectionEvents().collect { event ->
when (event) {
is PlaceSelectionSuccess -> Toast.makeText(
this@AutocompleteDemoActivity,
"Got place '${event.place.name}'",
Toast.LENGTH_SHORT
).show()
is PlaceSelectionError -> Toast.makeText(
this@AutocompleteDemoActivity,
"Failed to get place '${event.status.statusMessage}'",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/com/google/places/android/ktx/demo/Demo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.google.places.android.ktx.demo

import android.app.Activity
import androidx.annotation.StringRes

enum class Demo(
@StringRes val title: Int,
@StringRes val description: Int,
val activity: Class<out Activity>
) {
AUTOCOMPLETE_FRAGMENT_DEMO(
R.string.autocomplete_fragment_demo_title,
R.string.autocomplete_fragment_demo_description,
AutocompleteDemoActivity::class.java
),
PLACES_SEARCH_DEMO(
R.string.places_demo_title,
R.string.places_demo_description,
PlacesSearchDemoActivity::class.java
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.google.places.android.ktx.demo

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.ListView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class DemoActivity : AppCompatActivity() {

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

val listView = ListView(this).also {
it.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
it.adapter = DemoAdapter(this, Demo.values())
it.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
val demo = parent.adapter.getItem(position) as? Demo
demo?.let {
startActivity(Intent(this, demo.activity))
}
}
}
setContentView(listView)
}

private class DemoAdapter(context: Context, demos: Array<Demo>) :
ArrayAdapter<Demo>(context, R.layout.item_demo, demos) {

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val demoView = (convertView as? DemoItemView) ?: DemoItemView(context)
return demoView.also {
val demo = getItem(position)
it.title.setText(demo?.title ?: 0)
it.description.setText(demo?.description ?: 0)
}
}
}

private class DemoItemView(context: Context) : LinearLayout(context) {

val title: TextView by lazy { findViewById<TextView>(R.id.textViewTitle) }

val description: TextView by lazy { findViewById<TextView>(R.id.textViewDescription) }

init {
LayoutInflater.from(context)
.inflate(R.layout.item_demo, this)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ import dagger.hilt.android.HiltAndroidApp
class DemoApplication : Application() {
override fun onCreate() {
super.onCreate()

// Initialize the Places SDK. Note that the string value of `maps_api_key` will be generated
// at build-time (see app/build.gradle). The technique used here allows you to provide your
// API key such that the key is not checked in source control.
//
// See API Key Best Practices for more information on how to secure your API key:
// https://developers.google.com/maps/api-key-best-practices
Places.initialize(this, BuildConfig.PLACES_API_KEY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ class PlacesSearchDemoActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu, menu)
val searchView = menu.findItem(R.id.search).actionView as SearchView
searchView.also {
it.queryHint = getString(R.string.search_a_place)
it.isIconifiedByDefault = false
it.isFocusable = true
it.isIconified = false
it.requestFocusFromTouch()
it.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
searchView.apply {
queryHint = getString(R.string.search_a_place)
isIconifiedByDefault = false
isFocusable = true
isIconified = false
requestFocusFromTouch()
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
return false
}
Expand All @@ -88,11 +88,16 @@ class PlacesSearchDemoActivity : AppCompatActivity() {
}

private fun initRecyclerView() {
findViewById<RecyclerView>(R.id.recycler_view).also {
val layoutManager = LinearLayoutManager(this)
it.layoutManager = layoutManager
it.adapter = adapter
it.addItemDecoration(DividerItemDecoration(this, layoutManager.orientation))
val linearLayoutManager = LinearLayoutManager(this)
findViewById<RecyclerView>(R.id.recycler_view).apply {
layoutManager = linearLayoutManager
adapter = adapter
addItemDecoration(
DividerItemDecoration(
this@PlacesSearchDemoActivity,
linearLayoutManager.orientation
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.google.android.libraries.places.api.model.TypeFilter
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest
import com.google.android.libraries.places.api.net.PlacesClient
import com.google.android.libraries.places.ktx.api.net.awaitFindAutocompletePredictions
import com.google.android.libraries.places.ktx.api.net.findAutocompletePredictionsRequest
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
Expand All @@ -48,20 +49,23 @@ class PlacesSearchViewModel @ViewModelInject constructor(
_events.value = PlacesSearchEventError(throwable)
}
searchJob = viewModelScope.launch(handler) {
// Add delay so that network call is performed only after there is a 300 ms pause in the
// search query. This prevents network calls from being invoked if the user is still
// typing.
delay(300)

val bias: LocationBias = RectangularBounds.newInstance(
LatLng(37.7576948, -122.4727051), // SW lat, lng
LatLng(37.808300, -122.391338) // NE lat, lng
)

val request = FindAutocompletePredictionsRequest
.builder()
.setLocationBias(bias)
.setTypeFilter(TypeFilter.ESTABLISHMENT)
.setQuery(query)
.setCountries("US")
.build()
val request = findAutocompletePredictionsRequest {
setLocationBias(bias)
setTypeFilter(TypeFilter.ESTABLISHMENT)
setQuery(query)
setCountries("US")
}

val response = placesClient
.awaitFindAutocompletePredictions(request)
_events.value = PlacesSearchEventFound(response.autocompletePredictions)
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/res/layout/activity_autocomplete.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="@color/colorGray"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/autocomplete_fragment"
android:background="@android:color/white"
android:name="com.google.android.libraries.places.widget.AutocompleteSupportFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>
1 change: 1 addition & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
<color name="colorPrimary">#008577</color>
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#D81B60</color>
<color name="colorGray">#dedede</color>
</resources>
8 changes: 7 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@

<resources>
<string name="app_name">Android Places KTX Demo</string>
<string name="programmatic_place_predictions_instructions">" This activity demonstrates as-you-type programmatic place predictions. Tap on the search icon on the toolbar and search for a place."</string>
<string name="programmatic_place_predictions_instructions">This activity demonstrates as-you-type programmatic place predictions. Tap on the search icon on the toolbar and search for a place.</string>
<string name="search">Search</string>
<string name="search_a_place">Search a Place</string>

<string name="places_demo_title">Places Search Demo</string>
<string name="places_demo_description">Demonstrates usages of Places KTX coroutines and DSL builder features.</string>

<string name="autocomplete_fragment_demo_title">Autocomplete Fragment Demo</string>
<string name="autocomplete_fragment_demo_description">Demonstrates using AutocompleteSupportFragment and placeSelectionEvents() to use Kotlin Flow.</string>
</resources>
3 changes: 2 additions & 1 deletion places-ktx/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ android {
dependencies {
implementation deps.kotlin
implementation deps.kotlinxCoroutines.android
implementation deps.kotlinxCoroutines.playServices
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
api deps.kotlinxCoroutines.playServices
api deps.places

// Tests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.google.android.libraries.places.ktx.widget

import com.google.android.gms.common.api.Status
import com.google.android.libraries.places.api.model.Place
import com.google.android.libraries.places.widget.AutocompleteSupportFragment
import com.google.android.libraries.places.widget.listener.PlaceSelectionListener
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow

sealed class PlaceSelectionResult

data class PlaceSelectionSuccess(val place: Place) : PlaceSelectionResult()

data class PlaceSelectionError(val status: Status) : PlaceSelectionResult()

@ExperimentalCoroutinesApi
fun AutocompleteSupportFragment.placeSelectionEvents() : Flow<PlaceSelectionResult> =
callbackFlow {
this@placeSelectionEvents.setOnPlaceSelectedListener(object : PlaceSelectionListener {
override fun onPlaceSelected(place: Place) {
offer(PlaceSelectionSuccess(place))
}

override fun onError(status: Status) {
offer(PlaceSelectionError(status))
}
})
}

0 comments on commit 422c199

Please sign in to comment.