LiveData transformations and mediators

Alex Chen Jan 2026
1 tab
package com.example.myapp.ui

import androidx.lifecycle.*
import kotlinx.coroutines.launch

class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _userId = MutableLiveData<Int>()

    // Transform user ID to full user object
    val user: LiveData<User?> = _userId.switchMap { id ->
        liveData {
            val user = userRepository.getUser(id)
            emit(user)
        }
    }

    // Map user to display name
    val displayName: LiveData<String> = Transformations.map(user) { user ->
        user?.let { "${it.firstName} ${it.lastName}" } ?: "Unknown"
    }

    // Combine multiple LiveData sources
    private val _posts = MutableLiveData<List<Post>>()
    private val _comments = MutableLiveData<List<Comment>>()

    val feedItems: LiveData<FeedData> = MediatorLiveData<FeedData>().apply {
        var posts: List<Post>? = null
        var comments: List<Comment>? = null

        fun update() {
            if (posts != null && comments != null) {
                value = FeedData(posts!!, comments!!)
            }
        }

        addSource(_posts) { newPosts ->
            posts = newPosts
            update()
        }

        addSource(_comments) { newComments ->
            comments = newComments
            update()
        }
    }

    // Conditional transformation
    private val _searchQuery = MutableLiveData<String>()

    val searchResults: LiveData<List<Post>> = _searchQuery.switchMap { query ->
        if (query.isNullOrBlank()) {
            liveData { emit(emptyList<Post>()) }
        } else {
            liveData {
                val results = userRepository.searchPosts(query)
                emit(results)
            }
        }
    }

    // Custom transformation with distinct
    val postCount: LiveData<Int> = Transformations.distinctUntilChanged(
        Transformations.map(_posts) { it?.size ?: 0 }
    )

    fun setUserId(id: Int) {
        _userId.value = id
    }

    fun search(query: String) {
        _searchQuery.value = query
    }
}

data class FeedData(
    val posts: List<Post>,
    val comments: List<Comment>
)
1 file · kotlin Explain with highlit

LiveData transformations create derived data streams reactively. Transformations.map() converts values—like mapping User to username string. Transformations.switchMap() switches LiveData sources based on input, enabling dynamic queries. MediatorLiveData combines multiple LiveData sources, useful for aggregating data. The Mediator observes sources and updates when any changes. Transformations execute lazily only when observed. They maintain lifecycle awareness, preventing memory leaks. distinctUntilChanged() prevents duplicate emissions. These patterns eliminate manual observation management and enable declarative data flows. Transformations work with ViewModels to expose clean, transformed state to UI layers while keeping business logic separate.