onClick在带有数据绑定的MVVM中不起作用

时间:2019-12-05 02:18:28

标签: android android-fragments kotlin mvvm onclick

由于某些原因,onClick尚未在我的适配器中注册。我正在使用MVVM模式,并且确保所有部分都绑在一起,但是对于我一生来说,我不知道为什么这不起作用。

StoreFragment

package com.example.brandroidtest.main


import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.brandroidtest.databinding.FragmentStoreBinding


class StoreFragment : Fragment() {



    //Will Create a ViewModelProivders object of class DetailViewModel the first time viewModel is used
    //Allows us to move this code from on create to the declaration
    private val viewModel: StoreViewModel by lazy {
        val factory = StoreViewModelFactory(requireNotNull(activity).application)
        ViewModelProviders.of(this, factory).get(StoreViewModel::class.java)
    }


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {


        Log.i("onCreateView", "StoreFragment created")


        val binding = FragmentStoreBinding.inflate(inflater)



        binding.setLifecycleOwner(this)
        binding.viewModel = viewModel

        binding.storeList.adapter = StoreAdapter(StoreAdapter.OnClickListener {
            viewModel.displayStoreDetails(it)
            Log.i("inside OnClickListener", "after displayDetails")
        })

        Log.i("between adapter.onclick", "and viewModel observe")

        viewModel.selectedStore.observe(this, Observer {
            Log.i("observe", "inside the selectedStore observe method")
            if (null != it) {
                this.findNavController().navigate(
                    StoreFragmentDirections.actionMainListFragmentToDetailFragment(
                        it
                    )
                )
                viewModel.displayStoreDetailsComplete()
            }

        })



        return binding.root

    }

}

StoreViewModel

package com.example.brandroidtest.main

import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.network.StoreAPI
import kotlinx.coroutines.*


enum class StoreAPIStatus {LOADING, DONE, NO_CONNECTION}

class StoreViewModel(application: Application) : AndroidViewModel(application) {

    // Response from server: Either Store Data or Failure Message
    private val _status = MutableLiveData<StoreAPIStatus>()

    // for status of get request
    //displayed when there is no internet connection or if the connection is unstable and the data is being loaded
    val status: LiveData<StoreAPIStatus>
        get() = _status


    //internal variable accessed within this file
    private val listOfStores = MutableLiveData<List<Store>>()


    //external variable for anywhere else
    val stores: LiveData<List<Store>>
        get() = listOfStores


    private val _selectedStore = MutableLiveData<Store>()

    val selectedStore: LiveData<Store>
        get() = _selectedStore


    private var viewModelJob = Job()
    private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)


    /**
     * Call getStoreData() in init so we can display the result immediately.
     */
    init {


        Log.i("viewModel init", "inside StoreViewModel init block")
        if (isNetworkConnected(application.applicationContext))
            getStoreData()
        else
//            Log.i("Bypassed network call", "")
            listOfStores.value = emptyList()
            _status.value = StoreAPIStatus.NO_CONNECTION

    }
    /**
     * Sets the value of the status LiveData to the Store API data.
     */

    private fun getStoreData() {

        Log.i("getStoreData()", " inside getStoreData")


        coroutineScope.launch {
            try {
                Log.i("getStoreData()", "Inside the coroutine before getData")

               _status.value =  StoreAPIStatus.LOADING

                var storeData = async { StoreAPI.retrofitService.getData().stores }.await()

                Log.i("getStoreData()", "Inside the coroutine after getData")

                _status.value = StoreAPIStatus.DONE

                listOfStores.value = storeData

            } catch (e: Exception) {
                _status.value = StoreAPIStatus.NO_CONNECTION
                listOfStores.value = ArrayList()
                e.printStackTrace()
            }
        }

    }

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

    private fun isNetworkConnected(context: Context): Boolean {
        val cm =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
        return cm!!.activeNetworkInfo != null && cm.activeNetworkInfo.isConnected
    }

    //will be called to set the store as the one that was clicked
    fun displayStoreDetails(store : Store){
        Log.i("displayStoreDetails", "inside this method")
        _selectedStore.value = store
    }

    //sets the selected store's value to null so that live data can be updated when we select a new store and not show us the detail apge of the same store
    fun displayStoreDetailsComplete() {
        Log.i("displayStoreDetailsComplete", "inside this method")
        _selectedStore.value = null
    }

}

StoreAdapter

package com.example.brandroidtest.main

import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.databinding.ListItemBinding


class StoreAdapter(val onClickListener: OnClickListener) :
    ListAdapter<Store, StoreAdapter.StoreViewHolder>(DiffCallback) {
    class StoreViewHolder(private var binding: ListItemBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(store: Store) {
            binding.store = store
            Log.i("Adapter bind", store.storeLogoURL)
            binding.executePendingBindings()

        }
    }

    companion object DiffCallback : DiffUtil.ItemCallback<Store>() {
        override fun areItemsTheSame(oldItem: Store, newItem: Store): Boolean {
            return oldItem === newItem
        }

        override fun areContentsTheSame(oldItem: Store, newItem: Store): Boolean {
            return oldItem.storeID == newItem.storeID
        }
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): StoreViewHolder {
        return StoreViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context)))
    }

    override fun onBindViewHolder(holder: StoreViewHolder, position: Int) {

        val store = getItem(position)

        Log.i("inside onBindViewHolder", "")

        holder.itemView.setOnClickListener {
            Log.i("inside onBindViewHolder", "setOnClickListener")
            onClickListener.onClick(store)
        }

        holder.bind(store)
    }

    class OnClickListener(val clickListener: (store: Store) -> Unit) {

        fun onClick(store: Store) {
            Log.i("inside onClick", "click is being registered ${store.city}")
            return clickListener(store)
        }
    }
}

StoreDetailFragment

package com.example.brandroidtest.detailed


import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.example.brandroidtest.R
import com.example.brandroidtest.databinding.FragmentStoreDetailBinding


/**
 * A simple [Fragment] subclass.
 */
class StoreDetailFragment : Fragment() {


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        val application = requireNotNull(activity).application

        val binding = FragmentStoreDetailBinding.inflate(inflater)

        binding.setLifecycleOwner(this)

        val store = StoreDetailFragmentArgs.fromBundle(arguments!!).selectedStore

        val viewModelFactory = StoreDetailViewModelFactory(store, application)

        binding.viewModel = ViewModelProviders.of(this, viewModelFactory).get(StoreDetailViewModel::class.java)

        return binding.root

    }
}


StoreDetailViewModel

package com.example.brandroidtest.detailed

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store

class StoreDetailViewModel(store: Store, application: Application) : AndroidViewModel(application) {



    private val _selectedStore = MutableLiveData<Store>()

    val selectedStore : LiveData<Store>
        get() = _selectedStore


    init {
        _selectedStore.value = store
    }

}

我不知道为什么onClick不能正常工作,并且由于该原因而无法显示“详细信息片段”

这是项目链接:https://drive.google.com/open?id=1m8R8HvCt4m0KIp_IwdeO1YdB5yY8A8LK

1 个答案:

答案 0 :(得分:1)

问题来自适配器项目的布局。 每个显示项目的高度为wrap_content。但是您使用ScrollView作为项目视图的根视图。 删除无用的ScrollView和下一个LinearLayout。您的布局应如下所示:

<LinearLayout
    ...
    android:padding="16dp"/>

    <ImageView
        android:id="@+id/store_logo"
        .../>

    <LinearLayout
        android:id="@+id/store_detail"
        ...>
</LinearLayout>