简体   繁体   中英

How to load a LiveData from a LiveData?

In my database I have two lists: List of chats and list of users. Each item in the chat list contains the id of its respective user. I need to load each chat together with the user data to display it as a live data in a recyclerview. How to do this without breaking the MVVM patterns?

Database:

{
  "chats" : {
      "abc" : {
        "userId" : "123",
        "chatId" : "abc",
        "lastMessage" : "message1"
      },
      "cde" : {
        "userId" : "456",
        "chatId" : "def",
        "lastMessage" : "message2"
      }
    },
  "users" : {
    "abc" : {
      "name" : "Name1",
      "photo" : "photo.jpg",
      "userId" : "abc",
      "email" : "name1@gmail.com"
    },
    "def" : {
      "name" : "Name2",
      "photo" : "photo.jpg",
      "userId" : "def",
      "email" : "name2@gmail.com"
    }
  }
}  

ViewModel:

class ChatsViewModel:ViewModel() {

    private val chatsRepository = ChatsRepository()
    private var userRepository = UserRepository()
    fun fetchChatsData(): LiveData<MutableList<Chat>> {
        val mutableData = MutableLiveData<MutableList<Chat>>()
        chatsRepository.getChatsData().observeForever { chatsList ->
            mutableData.value = chatsList
        }
        return mutableData
    }
    fun fetchUserData(): LiveData<User> {
        val mutableData = MutableLiveData<User>()
        userRepository.getUserData("USER_ID_HERE").observeForever { user ->
            mutableData.value = user
        }
        return mutableData
    }

}

Fragment:

class ChatTabFragment2 : Fragment() {

    private lateinit var fragmentView: View
    private val viewModel by lazy { ViewModelProvider(this).get(ChatsViewModel::class.java) }
    private lateinit var adapter:ChatsAdapter


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        fragmentView = inflater.inflate(R.layout.fragment_chat_tab, container, false)

        adapter = ChatsAdapter{
            openChat(it.userId)
        }
        fragmentView.recycler_view.setHasFixedSize(true)
        fragmentView.recycler_view.adapter = adapter

        observeData()

        return fragmentView
    }

    private fun observeData() {
        viewModel.fetchChatsData().observe(viewLifecycleOwner, { chatsList ->
            chatsList.let {
             adapter.submitList(it)
            }
        })
    }

Chat Repository:

class ChatsRepository {

    private var chatsReference = FirebaseDatabase.getInstance().reference
            .child(UserFirebase.id)
    private lateinit var valueEventListener: ValueEventListener

    fun getChatsData(): LiveData<MutableList<Chat>> {
        val mutableData = MutableLiveData<MutableList<Chat>>()

        valueEventListener = object :ValueEventListener{
            override fun onDataChange(snapshot: DataSnapshot) {
                val chatsList = mutableListOf<Chat>()
                for (ds in snapshot.children) {
                    ds.getValue(Chat::class.java)?.let { chat ->
                        chatsList.add(chat)
                    }
                }
                mutableData.value = chatsList
            }

            override fun onCancelled(error: DatabaseError) {
                
            }

        }
        chatsReference.addValueEventListener(valueEventListener)
        return mutableData
    }
}

User Repository

class UserRepository {
    fun getUserData(userId:String): LiveData<User> {
        val mutableData = MutableLiveData<User>()
        FirebaseDatabase.getInstance().reference.child("usersRef").child(userId)
                .addListenerForSingleValueEvent(object :ValueEventListener{
                    override fun onDataChange(snapshot: DataSnapshot) {
                        mutableData.value = snapshot.getValue(User::class.java)
                    }
                    override fun onCancelled(error: DatabaseError) {}
                })
        return mutableData
    }
}

My suggestion would be to use different models for the data layer (firebase database) and the presentation layer (frontend). For example:

Let's call the models in the database ChatRemoteModel and UserRemoteModel .

data class ChatRemoteModel() {
    val userId: String,
    val chatId: String,
    val lastMessage: String
}

data class UserRemoteModel() {
    val name: String,
    val photo: String,
    val userId: String,
    val email: String   
}

Let's call the models you need for presentation Chat and User

data class Chat() {
    val user: User,
    val chatId: String,
    val lastMessage: String
}

data class User() {
    val name: String,
    val photo: String,
    val userId: String,
    val email: String   
}

Now in your repository classes, you retrieve data from the database and map them to the presentation models.

class ChatsRepository {

    ...

    private val userRepository = UserRepository()

    fun getChatsData(): LiveData<MutableList<Chat>> {
        val mutableData = MutableLiveData<MutableList<Chat>>()

        valueEventListener = object :ValueEventListener{
            override fun onDataChange(snapshot: DataSnapshot) {
                val chatsList = mutableListOf<Chat>()
                for (ds in snapshot.children) {
                    ds.getValue(ChatRemoteModel::class.java)?.let { chat ->
                        userRepository.getUser(chat.userId).observeForever { user ->
                            chatsList.add(
                                Chat(user, chat.chatId, chat.lastMessage)
                            )    
                        }                            
                    }
                }
                mutableData.value = chatsList
            }

            override fun onCancelled(error: DatabaseError) {
                
            }

        }
        chatsReference.addValueEventListener(valueEventListener)
        return mutableData
    }
}

You can return User instead of UserRemoteModel in the UserRepository class as well. This may seem like overkill as the two models are literally the same but it gives you the flexibility to design the presentation models as you require for the ViewModels.

So what I mean is if you have different models for different layers then you can design the models for the data layer to efficiently store data and transfer data, and design models for the presentation layer to efficiently display UI. You may want to improve the code,( Naming, prevent duplicate values, use mappers, use LiveData transformations, etc ), what I have provided is a minimal example to support my point.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM