I have a basic knowledge of Kotlin. I'm working on a small application. Since I need a variable in more than one function in my application, I just created a variable under the class and gave a default value to this variable. However, this variable should only work when the application is first opened. Is there a code where I can do this?
class Main : Fragment() {
var numara = 0 //the variable I am talking about
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_main, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
zikirCek.text = "${numara}"
zikirCek.setOnClickListener {
zikir(it)
}
sifirlaButton.setOnClickListener {
sifirla(it)
}
kaydetButton.setOnClickListener {
kaydetme(it)
}
kayitYukle.setOnClickListener {
kayityukle(it)
}
try {
arguments?.let {
//numara = mainArgs.fromBundle(it).numara.toInt()
var secilenId = MainArgs.fromBundle(it).id
context?.let {
val db = it.openOrCreateDatabase("Zikirler", Context.MODE_PRIVATE, null)
val cursor = db.rawQuery(
"SELECT * FROM zikirler WHERE id = ?",
arrayOf(secilenId.toString())
)
val zikirr = cursor.getColumnIndex("zikir")
while (cursor.moveToNext()) {
zikirCek.setText(cursor.getString(zikirr))
}
cursor.close()
}
}
} catch (e: Exception){
e.printStackTrace()
}
}
These are also the functions I mentioned
fun zikir(view: View){
numara = numara + 1
zikirCek.text = "${numara}"
}
fun sifirla(view: View){
numara = 0
zikirCek.text = "${numara}"
}
fun kaydetme(view: View){
val action = MainDirections.actionMainToKaydet(numara)
Navigation.findNavController(view).navigate(action)
}
fun kayityukle(view: View){
val actionn = MainDirections.actionMainToKayityukleme()
Navigation.findNavController(view).navigate(actionn)
}
}
You can store the variable using SharedPreferences in Android:
// Prepare a SharedPreference Object.
val sharedPreferences = getSharedPreferences("preferences", Context.MODE_PRIVATE)
// To save the variable
sharedPreferences.edit().putInt("numara", numara).apply()
// To retrieve the variable
val numara = sharedPreferences.getInt("numara", 0)
To understand the answer better, I recommend you read this:
Interface for accessing and modifying preference data returned by Context#getSharedPreferences. For any particular set of preferences, there is a single instance of this class that all clients share. Modifications to the preferences must go through an Editor object to ensure the preference values remain in a consistent state and control when they are committed to storage.
Related
Get numbers
class Base : Fragment() {
val time = ArrayList<Double>()
val amplitude = ArrayList<Double>()
var flag = 0
private fun readNumbersFromCSV(fileName: String) {
val textView: TextView = requireView().findViewById(R.id.result)
val timeTextView: TextView = requireView().findViewById(R.id.Time)
val amplitudeTextView: TextView = requireView().findViewById(R.id.Amplitude)
timeTextView.movementMethod = ScrollingMovementMethod()
amplitudeTextView.movementMethod = ScrollingMovementMethod()
try {
timeTextView.append("Time, s\n")
amplitudeTextView.append("Amplitude\n")
val file = File(fileName)
if(!file.exists()){
throw FileNotFoundException("File not found")
}
val reader = BufferedReader(FileReader(file))
var line = reader.readLine()
while (line != null) {
val parts = line.split(",")
if (parts.size == 2) {
time.add(parts[1].toDouble())
amplitude.add(parts[0].toDouble())
timeTextView.append(parts[1] + "\n")
amplitudeTextView.append(parts[0] + "\n")
}
line = reader.readLine()
}
flag = 1
reader.close()
} catch (e: FileNotFoundException) {
textView.text = "Error: File Not Found"
} catch (e: Exception) {
textView.text = "Error: ${e.message}"
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_base, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).toString()
val file = File(path, "data.csv").toString()
readNumbersFromCSV(file)
/*now im ready to pass data to another class*/
}
}
Do some calculations on those numbers
class Calculations : Fragment() {
private fun meanAmplitude(amplitudes: List<Double>): Double {
if(amplitudes.isEmpty()) return 3.5
return amplitudes.sum() / amplitudes.size
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_calculations, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val copiedList = Base().amplitude.toList() /* data from file passed to new array*/
val textViewAmp: TextView = view.findViewById(R.id.Camplitude)
val valueOfMean = meanAmplitude(copiedList).toString() /*calculate mean value*/
textViewAmp.text = valueOfMean /*display it*/
}
}
MyAdapter
internal class MyAdapter (var context: Context, fm: FragmentManager, var totalTabs: Int): FragmentPagerAdapter(fm) {
override fun getCount(): Int {
return totalTabs
}
override fun getItem(position: Int): Fragment {
return when(position){
0 -> {
Base()
}
1 -> {
Calculations()
}
2 -> {
About()
}
else -> getItem(position)
}
}
}
HomeActivity
class HomeActivity : AppCompatActivity() {
private lateinit var tabLayout: TabLayout
private lateinit var viewPager: ViewPager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
supportActionBar?.hide()
setContentView(R.layout.activity_home)
tabLayout = findViewById(R.id.tabLayout)
viewPager = findViewById(R.id.viewPager)
tabLayout.addTab(tabLayout.newTab().setText("Data"))
tabLayout.addTab(tabLayout.newTab().setText("Calculations"))
tabLayout.addTab(tabLayout.newTab().setText("About"))
tabLayout.tabGravity = TabLayout.GRAVITY_FILL
val adapter = MyAdapter(this, supportFragmentManager, tabLayout.tabCount)
viewPager.adapter = adapter
viewPager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tabLayout))
tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
viewPager.currentItem = tab!!.position
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
}
}
Im new in Kotlin. I have a problem with initializing an array that is being filled with data from a .csv file in the Base class, and then its contents should be passed to the Calculations class. The problem is that the array instance is being passed before it is being filled with numbers. Two fragments are generated probably in the same time.
Loading from file and initializing an array in the first class works, elements are displayed on the screen without any problems. After passing the array to the second class, it is empty.
I tried to do a flag, but it doesnt work like I though. Im not using activities, just Fragments and ViewPager. I tried Bundles but its hard to apply new things in my messy project.
Here:
val copiedList = Base().amplitude.toList()
You are instantiating a new instance of Base by calling its constructor. This new instance shares nothing with any previous instance. It's a brand new Base that hasn't done anything yet so its lists are still empty.
To pass data between fragments, you should create an arguments Bundle and pass that to the new fragment. The reason you need to do it this way is that Android automatically destroys and recreates Fragment instances under various conditions, and only the arguments data is preserved for the new instance.
The conventional way to do this is to define a Fragment factory function named newInstance() in its companion object. Then the Fragment can unpack the new data in onViewCreated(). You have to convert to and from DoubleArrays because Bundle doesn't support Lists.
class Calculations private constructor(): Fragment(R.layout.fragment_calculations) {
companion object {
private const val TIME_LIST_KEY = "timeList"
private const val AMP_LIST_KEY = "ampList"
fun newInstance(timeList: List<Double>, ampList: List<Double>) =
Calculations().apply {
arguments = bundleOf(
TIME_LIST_KEY to timeList.toDoubleArray(),
AMP_LIST_KEY to ampList.toDoubleArray()
)
}
}
private fun meanAmplitude(amplitudes: List<Double>): Double {
if(amplitudes.isEmpty()) return 3.5
return amplitudes.sum() / amplitudes.size
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val timeList = requireArguments().getDoubleArray(TIME_LIST_KEY).toList()
val ampList = requireArguments().getDoubleArray(AMP_LIST_KEY).toList()
val textViewAmp: TextView = view.findViewById(R.id.Camplitude)
val valueOfMean = meanAmplitude(ampList).toString() /*calculate mean value*/
textViewAmp.text = valueOfMean /*display it*/
}
}
Then in your first fragment, you use Calculations.newInstance() to create your second fragment before passing it to the transaction manager.
By the way, there's a major bug in your Base class. Since Fragment instances can be reused by the OS, the same fragment can go through multiple lifecycles. Since you are adding your data to the same ArrayLists every time onViewCreated() is called, they will get longer and longer as the user rotates the screen or navigates back and forth in the app. You should either remove those properties and use local variables instead, or you should clear those ArrayLists in onDestroyView().
I had a working app that does some arithmetic functionality that is out of the scope of the question, then I wanted to add more functionality to it, so i separated the layout into activity and fragment in order to later add other fragments that will do extra functions.
yet when I separated the layout taking some buttons along with a TextView (R.id.Result) to the new fragment, the text property of the TextView still updates as expected, but the display stays the same, always showing the initialization value initially assigned to it on its creation time.
I confirmed that the objects are the same as I expected them to be during runtime verified through logcat, what I need OFC is for the TextView display to update when I change its text property, numberInsertAction is called from the buttons properly and send proper data.
Important Note: below is only the relevant parts of code, it is much larger and I know what you see below can be simplified but it is built this way because of other classes and functionality that aren't shown below, if you need to see or ask about something outside the below code please do, yet again I only included the related part only and removed the business functionality.
Thanks in advance.
just to reiterate: numberInsertAction(view: View) is the entry point/function called by the buttons on the fragment.
MainActivity.kt
class MainActivity : AppCompatActivity(), AddObserverToActivity {
private lateinit var binding: ActivityMainBinding
private lateinit var stateManager: StateManager
override fun onCreate(savedInstanceState: Bundle?) {
//initialize layout
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val activityRoot = binding.root
setContentView(activityRoot)
stateManager = StateManager()
}
override fun addResultObserver(observer: Observer) {
Log.d(TAG, "addObserver! ${observer.toString()} ${observer::class.toString()}")
StateManager.addDisplayObserver(observer)
}
fun numberInsertAction(view: View) {
if (view is Button) {
StateManager.enterDigit(view.text.toString())
}
}
}
CalculatorFragment.kt
class CalculatorFragment : Fragment() {
companion object {
fun newInstance() = CalculatorFragment()
}
private lateinit var binding: FragmentCalculatorBinding
private lateinit var mainActivityHandle: AddObserverToActivity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d(TAG, "onCreateView")
binding = FragmentCalculatorBinding.inflate(inflater, container, false)
return inflater.inflate(R.layout.fragment_calculator, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "using on view created")
mainActivityHandle = context as AddObserverToActivity
Log.d(TAG, "${binding.Result} ${(binding.Result)::class.simpleName.toString()}")
Log.d(TAG, mainActivityHandle::class.toString())
mainActivityHandle.addResultObserver(DisplayPanel(binding.Result))
}
}
StateManager.kt
class StateManager : Observable() {
private val displayBuffer = DisplayBuffer(DecimalVariable("0"))
fun enterDigit(digit: String) {
Log.d(TAG, "enterDigit: $digit, $currentState")
displayBuffer.insertDigit(digit)
}
fun addDisplayObserver(observer: Observer) {
Log.d(TAG, "addDisplayObserver: $observer")
displayBuffer.addObserver(observer)
}
private fun doNotify(Notified: Any) {
Log.d(TAG, "doNotify: $Notified")
setChanged()
notifyObservers(Notified)
}
}
DisplayBuffer.kt
class DisplayBuffer(initializationValue: SomeClass) : Observable() {
private var initialValue = initializationValue
private var resultString = "0"
var value = initialValue
set(value) {
Log.d(TAG, "setter: $value")
field = value
doNotify()
}
fun set(value: String) {
Log.d(TAG, "set: $value")
this.value = value as Int
}
private fun doNotify() {
Log.d(TAG, "doNotify")
setChanged()
notifyObservers(value.toString())
}
fun insertDigit(digit: String) {
Log.d(TAG, "insertDigit: $digit result: $resultString")
resultString = resultString + digit
Log.d(TAG, "new value: $resultString")
setChanged()
notifyObservers(resultString)
}
}
DisplayPanel.kt
class DisplayPanel(calculationTextView: TextView) : Observer {
private val displayField: TextView = calculationTextView
private val maxDigits = 16
private fun setDisplay(text: String) {
Log.d(TAG, "setDisplay: $text")
if (text.length <= maxDigits) {
displayField.text = text
//displayField.invalidate()
}
}
override fun update(observable: Observable?, targetObjects: Any?) {
Log.d(TAG, "update: $this $observable, $targetObjects")
setDisplay(targetObjects as String)
}
}
Add binding.lifecycleOwner = viewLifecycleOwner in onCreateView or onViewCreated method.
was answered by #Mike M in Comments:
In CalculatorFragment,
He instructed me to change
return inflater.inflate(R.layout.fragment_calculator, container, false) to return binding.root.
as the problem was that this function inflated two instances of the fragment calculator layout and returned the later while it used the former as observer.
to qoute #Mike-M:
The inflater.inflate() call is creating a new instance of that layout that is completely separate from the one that FragmentCalculatorBinding is creating and using itself.
FragmentCalculatorBinding is inflating the view internally, which is why it is passed the inflater in its inflate() call.
I'm using Android Realm with sync, and when I do a basic query, it returns no items.
sync is enabled in build.gradle using:
realm {
syncEnabled = true
}
In my fragment (irrelevant bits removed):
private var adapter: MenuRealmAdapter? = null
private var realm: Realm? = null
private var menuItems: RealmResults<MenuItem>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
realm = App.mongoProxy.getSyncRealm()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_menu_list, container, false)
buildAdapter(view, RealmFacade.shared.emptyMenu as RealmResults<IListItem>)
buildMainMenu()
return view
}
override fun onDestroy() {
super.onDestroy()
menuItems?.removeAllChangeListeners()
realm?.close()
}
private fun buildMainMenu() {
menuItems = realm?.where<MenuItem>()?.findAllAsync()
menuItems?.addChangeListener{ items ->
// items is empty here //
adapter?.updateData(items as OrderedRealmCollection<IListItem>)
}
}
private fun buildAdapter(view: View, menuItems: OrderedRealmCollection<IListItem>) {
adapter = MenuRealmAdapter(menuItems, mListener, true)
val realmRecyclerView = binding!!.realmRecyclerView
realmRecyclerView.layoutManager =
LinearLayoutManager(view.context)
realmRecyclerView.adapter = adapter
}
MongoProxy.kt:
fun getSyncRealm (userPartition: Boolean = false): Realm? {
val user = app.currentUser() ?: return null
val partitionValue = getPartitionValue(userPartition)
Log.d(TAG, "Get realm for userPartition: $userPartition value: $partitionValue")
val config = SyncConfiguration.Builder(user, partitionValue)
.waitForInitialRemoteData()
.build()
return Realm.getInstance(config)
}
I have this working on iOS without issue, so I know the data and rules are set up fine, and the partition value gives me a sensible value, so I can't see where I'm going wrong.
facepalm moment - I had turned off sync to compact the database and forgot to turn it back on XD
I am missing some basic coding knowledge here I think, I want to present value to the fragment by assigning the function to the variable in a viewModel. When I call the function directly, I get correct value. When I assign function to variable and pass the variable to the fragment it is always null, why?
View Model
class CartFragmentViewModel : ViewModel() {
private val repository = FirebaseCloud()
private val user = repository.getUserData()
val userCart = user?.switchMap {
repository.getProductsFromCart(it.cart)
}
private fun calculateCartValue(): Long? {
val list = userCart?.value
return list?.map { it.price!! }?.sum()
}
//val cartValue = userCart?.value?.sumOf { it.price!! } <- THIS will be null
val cartValue = calculateCartValue() <- THIS will be null
val cartSize = userCart?.value?.size <- THIS will be null
}
Fragment
class CartFragment : RootFragment(), OnProductClick, View.OnClickListener {
private lateinit var cartViewModel: CartFragmentViewModel
private lateinit var binding: FragmentCartBinding
private val cartAdapter = CartAdapter(this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_cart,
container,
false
)
setAnimation()
cartViewModel = CartFragmentViewModel()
binding.buttonToCheckout.setOnClickListener(this)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerCart.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = cartAdapter
}
cartViewModel.userCart?.observe(viewLifecycleOwner, { list ->
cartAdapter.setCartProducts(list)
updateCart()
})
}
override fun onClick(view: View?) {
when (view) {
binding.buttonToCheckout -> {
navigateToCheckout(cartViewModel.cartValue.toString())
cartViewModel.sendProductEvent(
cartAdapter.cartList,
ProductEventType.CHECKOUT
)
}
}
}
override fun onProductClick(product: Product, position: Int) {
cartViewModel.removeFromCart(product)
cartAdapter.removeFromCart(product, position)
updateCart()
}
private fun updateCart() {
binding.textCartTotalValue.text = cartViewModel.cartValue.toString() <- NULL
binding.textCartQuantityValue.text = cartViewModel.cartSize.toString() <- NULL
}
}
Thanks!
It looks like userCart is some sort of observable variable which initially holds a null value and then gets populated with the data from your repository after the network call (or something similar) completes.
The reason that all your variables are null are because you are declaring their value immediately, so by the time those statements get executed, the network call hasn't yet completed and userCart?.value is null. However calling the calculateCartValue() function later on in the code might yield a value if the fetch is complete.
I'm trying to understand the concepts of MVVM but i'm having a hard time trying to understand how to communicate between The model class and UI (The fragment) in this case.
Here's the (shitty, be aware) code:
LoginFragment.kt
class LoginFragment: Fragment(), AuthListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
inflater,
R.layout.credentials_login_fragment,
container,
false
)
val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
val view: View = binding.root
val registerButton: Button = view.findViewById(R.id.register_button)
binding.viewModel = viewModel
viewModel.authListener = this
registerButton.setOnClickListener {
val transaction: FragmentTransaction? = fragmentManager?.beginTransaction()
transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)
}
override fun onStarted() {
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}
override fun onSuccess() {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}
LoginViewModel.kt
class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null
fun onLoginButtonClicked(view: View){
if(username.isNullOrEmpty() || password.isNullOrEmpty()){
authListener?.onError("Invalid username or password")
isCredentialsValid = false
return
}
if(!username.isNullOrEmpty() && password!!.length >= 8){
isCredentialsValid = true
authListener?.onSuccess()
}else{
authListener?.onError("Invalid")
}
}}
Lets assume now that I enter an username and password and both meet the criteria. Now i'd like to, when i click on the "Log in" button, the current fragment is replaced by a menu fragment, for example.
How could i achieve something like that ? I've tried to replace from the ViewModel class, but that doesn't work.
Should I take the result of "isCredentialsValid" from the VM class and respond accordingly in the LoginFragment class ?
Thank you.
You have to use live data for updating the data from viewModel to view. I will post the code how it should be, but make sure that you need to understand the concept of LiveData.
LoginViewModel.kt
class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null
// LiveData to udpate the UI
private val _isValidCredential = MutableLiveData<Boolean>()
val isValidCredential: LiveData<Boolean> = _isValidCredential
fun onLoginButtonClicked(view: View){
if(username.isNullOrEmpty() || password.isNullOrEmpty()){
authListener?.onError("Invalid username or password")
isCredentialsValid = false
return
}
if(!username.isNullOrEmpty() && password!!.length >= 8){
isCredentialsValid = true
// to update the value of live data wherever you need
_isValidCredential.value = true
authListener?.onSuccess()
}else{
authListener?.onError("Invalid")
// to update the value of live data wherever you need
_isValidCredential.value = false
}
}
}
Your Fragment should be
LoginFragment.kt
class LoginFragment: Fragment(), AuthListener {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
inflater,
R.layout.credentials_login_fragment,
container,
false
)
val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
val view: View = binding.root
val registerButton: Button = view.findViewById(R.id.register_button)
binding.viewModel = viewModel
viewModel.authListener = this
// This is the way you need to observe the value
viewModel.isValidCredential.observe(viewLifecycleOwner, Observer {
if(it){
// do your navigation stuff here
}else{
// do your stuff if not valid credential
}
})
registerButton.setOnClickListener {
val transaction: FragmentTransaction? =
fragmentManager?.beginTransaction()
transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
}
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)
}
override fun onStarted() {
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}
override fun onSuccess() {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}
override fun onError(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}
A typical way of communicating back to the UI from the view model is using livedata. In your LoginViewModel, you would set your livedata to either true or false. Inside your view LoginFragment.kt you would have an observer. This observers job is to fire anytime a livedata's value has changed. That way you can have logic in your view that can either shows an error message liveData = false or launch the menu fragment = true.
Here is a good example using livedata to pass data to the view (fragment) this in the docs: https://developer.android.com/topic/libraries/architecture/viewmodel#implement