4

I wish to know when button clicks on fragment and update MainActivity. This code works fine, but when screen rotated due to Activity LifeCycle OnCreate called again and viewModel.nav.observe called again with new value true, but I wish it to be called only onButton click. What I need is onClick the value will be set to true and then false, so I only know when button clicked and not on screen rotate. Or some other way to know the button is clicked in the fragment.

How Can I achieve this?

Fragment

class Fragment1 : Fragment() {

    companion object {
        fun newInstance() = Fragment1()
    }

    private lateinit var viewModel: SharedViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_1, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        
        buttonNextF1.setOnClickListener {
            viewModel.nav.value = true
        }
    }
}

MainActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val viewModel = ViewModelProvider(this).get(SharedViewModel::class.java)

        var fragment1 = Fragment1.newInstance()
        navigateToFragment(fragment1)

        viewModel.nav.observe(this, {
            Log.d("DTAG", "Status: $it")
        })
    }

    private fun navigateToFragment(fragment: Fragment) {

        val fragmentTransaction = supportFragmentManager.beginTransaction()
        fragmentTransaction.addToBackStack(null)
        fragmentTransaction.replace(R.id.mainFrame, fragment)
        fragmentTransaction.commit()

    }
}

ViewModel

class SharedViewModel : ViewModel() {
    
    var nav = MutableLiveData(false)
}
Dim
  • 4,527
  • 15
  • 80
  • 139

3 Answers3

3

You can use SingleLiveEvent to achieve your goal because:

  • You only want to notify the event (click button on fragment) once
  • There is only one observer (MainActivity) that observe on the LiveData

Step 1: Create a SingleLiveEvent.kt file

class SingleLiveEvent<T> : MutableLiveData<T?>() {
    private val mPending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        if (hasActiveObservers()) {
            Log.w(
                TAG,
                "Multiple observers registered but only one will be notified of changes."
            )
        }
        // Observe the internal MutableLiveData
        super.observe(owner, Observer { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }

    companion object {
        private const val TAG = "SingleLiveEvent"
    }
}

Step 2: In SharedViewModel

class SharedViewModel : ViewModel() {
    var nav = SingleLiveEvent<Boolean>()
}

Step 3: In MainActivity

viewModel.nav.observe(this, Observer {
    Log.d("DTAG", "Status: $it")
})
Son Truong
  • 13,661
  • 5
  • 32
  • 58
1

I don't like this solution, but this code lab says, you should post "navigation handled" event back to view model.

View model implementation:

interface MyViewModel {
    val navigationEvent: LiveData<Unit?>
    
    fun onNavigationButtonClicked()
    fun onNavigationDone()
}

class MyViewModelImpl : ViewModel(), MyViewModel {
    
    override val navigationEvent = MutableLiveData<Unit?>()

    override fun onNavigationButtonClicked() {
        navigationEvent.value = Unit
    }

    override fun onNavigationDone() {
        navigationEvent.value = null
    }
}

View model usage

vm.navigationEvent.observe(lifecycleOwner, { event ->
    // check event is not handled yet
    event ?: return@observe
    // make transition
    findNavController().navigate(direction)
    // notify view model navigation event is handled
    vm.onNavigationDone()
})
Sergei Bubenshchikov
  • 5,275
  • 3
  • 33
  • 60
0

Just use saveStateViewMode, document here

Dean
  • 186
  • 5