Implementing Google places autoComplete textfield implementation in jetpack compose android
Asked Answered
Y

3

11

Did anyone implement google autocomplete suggestion text field or fragment in a jetpack compose project? If so kindly guide or share code snippets as I'm having difficulty in implementing it.

Update

Here is the intent that I'm triggering to open full-screen dialog, but when I start typing within it gets closed, and also I'm unable to figure out what the issue is and need a clue about handling on activity result for reading the result of the predictions within this compose function.

    Places.initialize(context, "sa")

    val fields = listOf(Place.Field.ID, Place.Field.NAME)

    val intent = Autocomplete.IntentBuilder(
        AutocompleteActivityMode.FULLSCREEN,fields).build(context)

        startActivityForResult(context as MainActivity,intent, AUTOCOMPLETE_REQUEST_CODE, Bundle.EMPTY)
                            
Yun answered 24/1, 2022 at 13:40 Comment(2)
What difficulty? Where's your own attempt?Propinquity
@JamesZ kindly have a look at the update, and let me know if you can help or suggest anything about the fix.Yun
C
14

I am using the MVVM architecture and this is how I implemented it:

GooglePlacesApi

I've created an api for reaching google api named GooglePlacesApi
interface GooglePlacesApi {
    @GET("maps/api/place/autocomplete/json")
    suspend fun getPredictions(
        @Query("key") key: String = <GOOGLE_API_KEY>,
        @Query("types") types: String = "address",
        @Query("input") input: String
    ): GooglePredictionsResponse

    companion object{
        const val BASE_URL = "https://maps.googleapis.com/"
    }
}

The @Query("types") field is for specifiying what are you looking for in the query, you can look for establishments etc. Types can be found here

Models

So I created 3 models for this implementation:
GooglePredictionsResponse
The way the response looks if you are doing a GET request with postman is:

Google Prediction Response

You can see that we have an object with "predictions" key so this is our first model.

data class GooglePredictionsResponse(
    val predictions: ArrayList<GooglePrediction>
)
GooglePredictionTerm
data class GooglePredictionTerm(
    val offset: Int,
    val value: String
)
GooglePrediction
data class GooglePrediction(
    val description: String,
    val terms: List<GooglePredictionTerm>
)

I only needed that information, if you need anything else, feel free to modify the models or create your own.

GooglePlacesRepository

And finally we create the repository to get the information (I'm using hilt to inject my dependencies, you can ignore those annotations if not using it)
@ActivityScoped
class GooglePlacesRepository @Inject constructor(
    private val api: GooglePlacesApi,
){
    suspend fun getPredictions(input: String): Resource<GooglePredictionsResponse>{
        val response = try {
            api.getPredictions(input = input)
        } catch (e: Exception) {
            Log.d("Rently", "Exception: ${e}")
            return Resource.Error("Failed prediction")
        }

        return Resource.Success(response)
    }
}

Here I've used an extra class I've created to handle the response, called Resource

sealed class Resource<T>(val data: T? = null, val message: String? = null){
    class Success<T>(data: T): Resource<T>(data)
    class Error<T>(message: String, data:T? = null): Resource<T>(data = data, message = message)
    class Loading<T>(data: T? = null): Resource<T>(data = data)
}

View Model

Again I'm using hilt so ignore annotations if not using it.
@HiltViewModel
class AddApartmentViewModel @Inject constructor(private val googleRepository: GooglePlacesRepository): ViewModel(){

    val isLoading = mutableStateOf(false)
    val predictions = mutableStateOf(ArrayList<GooglePrediction>())
    
    fun getPredictions(address: String) {
        viewModelScope.launch {
            isLoading.value = true
            val response = googleRepository.getPredictions(input = address)
            when(response){
                is Resource.Success -> {
                    predictions.value = response.data?.predictions!!
                }
            }

            isLoading.value = false
        }
    }

    fun onSearchAddressChange(address: String){
        getPredictions(address)
    }
}

If you need any further help let me know

  • I didn't include UI implementation because I assume it is individual but this is the easier part ;)
Chura answered 11/6, 2022 at 16:30 Comment(1)
To improve even more, you could create a state holder data class AddApartmentUiState(val isLoading: Boolean = false, val predictions: ArrayList<GooglePrediction> = arrayListOf()). On the view model private val _uiState = MutableStateFlow(AddApartmentUiState()); val uiState: StateFlow<AddApartmentUiState> = _uiState.asStateFlow(). To change predictions value use: _uiState.updateUnderworld
F
3
@Composable
fun MyComponent() {
    val context = LocalContext.current

    val intentLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult()
    ) {
        when (it.resultCode) {
            Activity.RESULT_OK -> {
                it.data?.let {
                    val place = Autocomplete.getPlaceFromIntent(it)
                    Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
                }
            }
            AutocompleteActivity.RESULT_ERROR -> {
                it.data?.let {
                    val status = Autocomplete.getStatusFromIntent(it)
                    Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
                }
            }
            Activity.RESULT_CANCELED -> {
                // The user canceled the operation.
            }
        }
    }

    val launchMapInputOverlay = {
        Places.initialize(context, YOUR_API_KEY)
        val fields = listOf(Place.Field.ID, Place.Field.NAME)
        val intent = Autocomplete
            .IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
            .build(context)
        intentLauncher.launch(intent)
    }

    Column {
        Button(onClick = launchMapInputOverlay) {
            Text("Select Location")
        }
    }
}
Fourpenny answered 19/12, 2022 at 16:17 Comment(0)
K
1

Just use Google Maps Places API library

implementation("com.google.android.libraries.places:places:3.1.0")

Initialize it in .Application file in onCreate()

Places.initialize(applicationContext, "YOUR_API_KEY")

Then invoke the function on every textField input

val client = Places.createClient(androidContext())

suspend fun getAddressPredictions(
    sessionToken: AutocompleteSessionToken = AutocompleteSessionToken.newInstance(),
    inputString: String,
    location: LatLng? = null
) = suspendCoroutine<List<AutocompletePrediction>> {

    placesClient.findAutocompletePredictions(
            FindAutocompletePredictionsRequest.builder()
                .setOrigin(location)
                // Call either setLocationBias() OR setLocationRestriction().
                // .setLocationBias(bounds)
                // .setLocationRestriction(bounds)
                .setCountries("US")
                .setTypesFilter(listOf(PlaceTypes.ADDRESS))
                .setSessionToken(sessionToken)
                .setQuery(inputString)
                .build()
    ).addOnCompleteListener { completedTask ->
        if (completedTask.exception != null) {
            Log.e([email protected](), completedTask.exception?.stackTraceToString().orEmpty())
            it.resume(listOf())
        } else {
            it.resume(completedTask.result.autocompletePredictions)
        }
    }
}

Want to get place LatLng? Here is how

Krystenkrystin answered 9/10, 2023 at 7:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.