You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LiveData分层数据结构设计及DataBinding下UI重绘优化问询

How to Structure Hierarchical LiveData for DataBinding & Minimize UI Redraws

Great question—this is a super common pain point when working with nested data structures in LiveData + DataBinding, especially when using Room to fetch hierarchical data. Let's break this down step by step, starting with structuring your data, then solving the partial redraw issue for your Workout Fragment.

1. Building a Reasonable Hierarchical Data Structure for LiveData + DataBinding

The core idea is to avoid monolithic "god objects" that force full UI redraws when any tiny detail changes. Instead, design each layer of your data to be independently observable, so changes only propagate to the relevant UI components:

  • Make each entity observable: Have Workout, Group, Lift, and Set extend BaseObservable (for Java) or use observable property wrappers (for Kotlin). This lets DataBinding listen to changes on individual properties without needing the entire parent object to refresh.
  • Use observable lists for child collections: Replace regular List/ArrayList with ObservableArrayList for nested collections (like Group.lifts or Lift.sets). This tells DataBinding to update only added/removed/moved items in the UI, not rebuild the entire container.
  • Keep Room entities separate: Don’t cram all nested data into a single Room entity. Use Room's relationships (like @Relation) to fetch the full hierarchy, then map those immutable Room entities to your observable data classes in your repository.

Example Kotlin implementation for a Set:

class Set : BaseObservable() {
    @get:Bindable
    var weight: Double = 0.0
        set(value) {
            field = value
            notifyPropertyChanged(BR.weight)
        }

    @get:Bindable
    var reps: Int = 0
        set(value) {
            field = value
            notifyPropertyChanged(BR.reps)
        }
}

And a Lift with an observable list of sets:

class Lift : BaseObservable() {
    @get:Bindable
    var name: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }

    val sets = ObservableArrayList<Set>()
}

2. Minimizing UI Redraws for Partial Changes in Your Workout Fragment

Your current problem—full UI redraws when a single Set changes, or focus loss when splitting into multiple LiveData streams—can be solved by combining observable entities with targeted Room updates. Here's how:

Step 1: Map Room's Immutable Entities to Observable Classes

When you fetch the full Workout->Group->Lift->Set hierarchy from Room, convert those immutable data classes into your observable versions (like the Set and Lift examples above). This way, changes to individual properties trigger only the relevant UI updates, not a full LiveData emission.

In your repository, use Transformations.map to handle this conversion:

fun getWorkoutWithObservableHierarchy(workoutId: Long): LiveData<Workout> {
    return Transformations.map(workoutDao.getWorkoutWithHierarchy(workoutId)) { roomWorkout ->
        roomWorkout.toObservableWorkout()
    }
}

// Extension function to convert Room's entity to observable Workout
private fun RoomWorkout.toObservableWorkout(): Workout {
    val workout = Workout().apply { date = this@toObservableWorkout.date }
    
    roomWorkout.groups.forEach { roomGroup ->
        val group = Group().apply { title = roomGroup.title }
        
        roomGroup.lifts.forEach { roomLift ->
            val lift = Lift().apply { name = roomLift.name }
            
            roomLift.sets.forEach { roomSet ->
                lift.sets.add(Set().apply {
                    weight = roomSet.weight
                    reps = roomSet.reps
                })
            }
            
            group.lifts.add(lift)
        }
        
        workout.groups.add(group)
    }
    
    return workout
}

Step 2: Bind to Observable Properties in Layouts

In your DataBinding layout, bind directly to the observable properties of each entity. For example, a TableRow for a Set would look like this:

<TableRow>
    <EditText
        android:text="@={String.valueOf(set.weight)}"
        android:inputType="numberDecimal" />
    <EditText
        android:text="@={String.valueOf(set.reps)}"
        android:inputType="number" />
</TableRow>

When you update a Set's weight or reps, notifyPropertyChanged(BR.weight) tells DataBinding to refresh only that EditText—no full UI redraw.

Step 3: Handle Add/Remove Actions Without Losing Focus

Since you're using ObservableArrayList for Group.lifts and Lift.sets, adding a new Lift or Set will trigger DataBinding to insert the new UI element without rebuilding the entire TableLayout or LinearLayout. This preserves input focus because existing views aren't destroyed and recreated.

For example, adding a Set to a Lift in your ViewModel:

fun addSetToLift(lift: Lift) {
    lift.sets.add(Set().apply { weight = 0.0; reps = 0 })
    // No need to update the parent LiveData—ObservableArrayList handles the UI update
}

Step 4: Optimize Room Updates to Avoid Full Hierarchy Refreshes

Instead of updating the entire Workout entity when a Set changes, update only the Set entity in Room. Your repository can listen to individual Set updates and sync them to the observable Lift.sets list, instead of re-emitting the entire Workout LiveData. This avoids unnecessary LiveData emissions entirely.

You can use a MediatorLiveData in your repository to listen to both the initial Workout fetch and individual Set updates, then update the observable list in place.

Key Takeaways

  • Avoid monolithic LiveData objects: Break your hierarchy into observable entities so changes only affect the relevant UI.
  • Use observable lists: ObservableArrayList ensures add/remove actions only update the affected UI elements, preserving focus.
  • Map Room entities to observable classes: Separate immutable database models from mutable UI-bound models to control when updates propagate.

内容的提问来源于stack exchange,提问作者user1713450

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:05:35