disi
disi

Reputation: 1

How can a ViewModel function access the Place instance returned by PlaceSelectionListener of AutocompleteSupportFragment?

I am building an Android app using Kotlin.

I have a layout that contains a create_button. When the user clicks the button, the onClick should call onCreateJourney() in my ViewModel. This function requires the selectedPlaceId parameter to update a Journey entity with its value.

In my Fragment, I use the AutocompleteSupportFragment for the user to search and choose the desired place. This works well, as onPlaceSelected() returns the correct place in response to the user's selection. But I can't figure out how to use the returned place id value (p0.id) in my ViewModel onCreateJourney().

At this stage, the compiler throws this error:

cannot find method onCreateJourney() in class com.example.traveljournal.journey.NewJourneyViewModel

Please, can you shed some light on this issue for me?

My UI (.xml fragment layout):

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".journey.NewJourneyFragment">

    <data>
        <variable
            name="newJourneyViewModel"
            type="com.example.traveljournal.journey.NewJourneyViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/whereQuestion"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="10dp"
            android:text="@string/whereQuestion"
            android:textSize="18sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.027"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/journeyBckgImageView" />

        <androidx.cardview.widget.CardView
            android:id="@+id/cardView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent">

        </androidx.cardview.widget.CardView>

        <fragment
            android:id="@+id/autocomplete_fragment"
            android:name="com.google.android.libraries.places.widget.AutocompleteSupportFragment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/whereQuestion" />

        <Button
            android:id="@+id/create_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:onClick="@{() -> newJourneyViewModel.onCreateJourney()}"
            android:text="@string/createJourneyButton"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

My ViewModel

class NewJourneyViewModel (
        private val journeyKey: Long = 0L,
        val database: TravelDatabaseDao) : ViewModel() {

    private var viewModelJob = Job()

    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)

    private val _navigateToJourneys = MutableLiveData<Boolean?>()

    val navigateToJourneys: LiveData<Boolean?>
        get() = _navigateToJourneys

    fun doneNavigating() {
        _navigateToJourneys.value = null
    }

    fun onCreateJourney(selectedPlaceId: String) {
        uiScope.launch {
            withContext(Dispatchers.IO) {
                val journey = database.getJourney(journeyKey) ?: return@withContext
                journey.placeId = selectedPlaceId
                database.updateJourney(journey)
            }
            _navigateToJourneys.value = true
        }
    }

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

My Fragment

class NewJourneyFragment : Fragment(), PlaceSelectionListener {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        (activity as AppCompatActivity).supportActionBar?.title = getString(R.string.createJourney)

        val binding: FragmentNewJourneyBinding = DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_new_journey, container, false
        )

        val application = requireNotNull(this.activity).application

        val arguments = NewJourneyFragmentArgs.fromBundle(arguments!!)

        val dataSource = TravelDatabase.getInstance(application).travelDatabaseDao

        val viewModelFactory = NewJourneyViewModelFactory(arguments.journeyKey, dataSource)

        val newJourneyViewModel =
            ViewModelProviders.of(
                this, viewModelFactory).get(NewJourneyViewModel::class.java)

        binding.newJourneyViewModel = newJourneyViewModel

        newJourneyViewModel.navigateToJourneys.observe(this, Observer {
            if(it == true) {
                this.findNavController().navigate(
                    NewJourneyFragmentDirections.actionNewJourneyDestinationToJourneysDestination())
                newJourneyViewModel.doneNavigating()
            }
        })

        if (!Places.isInitialized()) {
            this.context?.let { Places.initialize(it, getString(R.string.apiKey), Locale.US) }
        }

        val autocompleteFragment = childFragmentManager.findFragmentById(R.id.autocomplete_fragment)
                as? AutocompleteSupportFragment
        autocompleteFragment?.setOnPlaceSelectedListener(this)
        autocompleteFragment!!.setHint(getString(R.string.destinationExample))
        autocompleteFragment!!.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.NAME))

        return binding.root
    }

    @ExperimentalStdlibApi
    override fun onPlaceSelected(p0: Place) {
        Log.i("PLACE", "Place: " + p0.name + ", " + p0.id)
    }

    override fun onError(status: Status) {
        Toast.makeText(this.context,""+status.toString(),Toast.LENGTH_LONG).show()
        Log.i("ERROR", "An error occurred: " + status)
    }
}

Upvotes: 0

Views: 192

Answers (2)

disi
disi

Reputation: 1

In NewJourneyViewModel, I set up selectedPlaceId as MutableLiveData.

val selectedPlaceId = MutableLiveData<String>()

In NewJourneyFragment, I used lateinit to create a field for NewJourneyViewModel called newJourneyViewModel and initialized it in onCreateView().

private lateinit var newJourneyViewModel : NewJourneyViewModel

Now having access to my ViewModel outside of onCreateView(), in my Fragment onPlaceSelected() method I can set the value of selectedPlaceId to p0.id.

newJourneyViewModel.selectedPlaceId.value = p0.id

If anybody can come up with a better and safer approach on this, it will be highly appreciated.

Upvotes: 0

kgandroid
kgandroid

Reputation: 5595

You have to pass the field selectedPlaceId as an argument in the xml :

android:onClick="@{() -> newJourneyViewModel.onCreateJourney(newJourneyViewModel.selectedPlaceId)}"

How will you get the selectedPlaceId in view model depends on your programming logic.

For testing purposes, you can hardcode the value.

Upvotes: 0

Related Questions