You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android项目:寻求替代ViewModel中postDelayed的更优初始化方案

Better Alternatives to postDelayed for Data Loading in DetailViewModel

Hey there, that fixed 100ms postDelayed hack is a fragile solution—it relies on arbitrary timing that can fail on slower devices or break if initialization logic changes later. Let's walk through cleaner, more reliable fixes tailored to your code structure:

The root issue likely stems from RemoteDataSource not being fully injected when your DetailViewModel initializes. Ensure your ViewModel receives its dependencies directly via Dagger, so injection completes before the ViewModel is created.

Step 1: Update ViewModel Constructor

Make RemoteDataSource a direct constructor dependency (your concrete ViewModel implementation will receive it via Dagger):

abstract class DetailViewModel(
    private val item: TmdbItem,
    private val remoteDataSource: RemoteDataSource // Inject directly
) : BaseViewModel() {
    // Remove the Handler and delayed init code
    init {
        // Call methods immediately—dependencies are already injected
        showTrailers()
        showCast()
    }

    // ... rest of your ViewModel code
}

Step 2: Use Dagger to Create ViewModels

Create a Dagger-backed ViewModel Factory to ensure all dependencies are injected when the ViewModel is instantiated:

class DaggerViewModelFactory @Inject constructor(
    private val viewModels: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val provider = viewModels[modelClass] 
            ?: throw IllegalArgumentException("Unknown ViewModel class: $modelClass")
        return provider.get() as T
    }
}

Step 3: Inject the Factory in Your Fragment

Update your DetailFragment to use the factory to get the ViewModel:

abstract class DetailFragment<T : TmdbItem> : BaseDaggerFragment(), CastClickCallback {
    @Inject lateinit var viewModelFactory: DaggerViewModelFactory

    override fun getViewModel(): DetailViewModel {
        return ViewModelProvider(this, viewModelFactory)[YourConcreteDetailViewModel::class.java]
    }

    // ... rest of your Fragment code
}

This guarantees RemoteDataSource is fully initialized before your ViewModel calls showTrailers() or showCast().

2. Tie Data Loading to Fragment Lifecycle

If you prefer to load data only when the Fragment is visible, use Android's LifecycleObserver to trigger loading at a safe lifecycle event (like ON_START):

Update ViewModel to Observe Lifecycle

abstract class DetailViewModel(private val item: TmdbItem) : BaseViewModel(), LifecycleObserver {
    // Remove Handler and delayed init code

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun loadData() {
        showTrailers()
        showCast()
    }

    // ... rest of your ViewModel code
}

Bind ViewModel to Fragment Lifecycle

In your DetailFragment's onViewCreated:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val viewModel = getViewModel()
    // Attach ViewModel to Fragment's lifecycle
    viewLifecycleOwner.lifecycle.addObserver(viewModel)
    
    // ... rest of your view setup code
}

This ensures data loads only when the Fragment is in a stable, visible state, avoiding any race conditions with initialization.

3. Listen for RemoteDataSource Initialization (If Async Init is Needed)

If RemoteDataSource has async initialization logic (e.g., setting up a network client), expose a LiveData signal to notify when it's ready:

Update RemoteDataSource

class RemoteDataSource {
    private val _isInitialized = MutableLiveData<Boolean>()
    val isInitialized: LiveData<Boolean> = _isInitialized

    // Your async initialization logic
    fun initialize() {
        // Simulate async setup (e.g., Retrofit client init)
        CoroutineScope(Dispatchers.IO).launch {
            // Do async work
            _isInitialized.postValue(true)
        }
    }
}

Update ViewModel to Listen for Ready Signal

abstract class DetailViewModel(
    private val item: TmdbItem,
    private val remoteDataSource: RemoteDataSource
) : BaseViewModel() {
    init {
        remoteDataSource.isInitialized.observeForever { isReady ->
            if (isReady) {
                showTrailers()
                showCast()
                // Clean up observer to avoid memory leaks
                remoteDataSource.isInitialized.removeObserver(this)
            }
        }
        // Trigger initialization if needed
        remoteDataSource.initialize()
    }

    // ... rest of your ViewModel code
}

Avoiding fixed time delays makes your code more reliable and easier to maintain. The Dagger injection fix is the cleanest solution if the issue is just timing with dependency injection. If you need to align data loading with UI visibility, the LifecycleObserver approach is ideal.

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

火山引擎 最新活动