main

square/leakcanary

Last updated at: 29/12/2023 09:39

ClientAppsScreen.kt

TLDR

The file ClientAppsScreen.kt contains the implementation of a screen for displaying a list of client apps. It includes a view model, state management, and composables for rendering the screen.

Classes

ClientAppsViewModel

The ClientAppsViewModel class is a view model responsible for fetching the list of client apps from the HeapRepository. It also handles navigation to the client app analysis screen through the navigator dependency.

ClientApp (data class)

The ClientApp data class represents a client app and contains the packageName and leakCount properties.

ClientAppState (sealed interface)

The ClientAppState sealed interface represents the different states of the client apps screen. It has two implementations: Loading and Success. The Loading state indicates that the app list is being loaded, while the Success state represents a successful loading with a list of ClientApp instances.

Methods

onAppClicked(app: ClientApp)

The onAppClicked method is called when a client app is clicked. It triggers the navigation to the client app analysis screen using the navigator dependency.

ClientAppList(apps: List<ClientApp>, onAppClicked: (ClientApp) -> Unit)

The ClientAppList composable function renders a lazy column list of client apps. Each client app item is clickable and displays the app's package name and leak count.

ClientAppsScreen(viewModel: ClientAppsViewModel = viewModel())

The ClientAppsScreen composable function is the entry point of the client apps screen. It takes an optional ClientAppsViewModel parameter and uses it to observe the state of the screen. It renders different UI components based on the screen state.

package org.leakcanary.screens

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.leakcanary.WhileSubscribedOrRetained
import org.leakcanary.data.HeapRepository
import org.leakcanary.screens.ClientAppState.Loading
import org.leakcanary.screens.ClientAppState.Success
import org.leakcanary.screens.Destination.ClientAppAnalysesDestination

data class ClientApp(val packageName: String, val leakCount: Int)

sealed interface ClientAppState {
  object Loading : ClientAppState
  class Success(val clientApps: List<ClientApp>) : ClientAppState
}

@HiltViewModel
class ClientAppsViewModel @Inject constructor(
  private val repository: HeapRepository,
  private val navigator: Navigator
) : ViewModel() {

  val state = stateStream().stateIn(
    viewModelScope,
    started = WhileSubscribedOrRetained,
    initialValue = Loading
  )

  fun onAppClicked(app: ClientApp) {
    navigator.goTo(ClientAppAnalysesDestination(app.packageName))
  }

  private fun stateStream() = repository.listClientApps()
    .map { app -> Success(app.map { ClientApp(it.package_name, it.leak_count.toInt()) }) }
}

@Composable
fun ClientAppsScreen(
  viewModel: ClientAppsViewModel = viewModel(),
) {
  val clientAppState: ClientAppState by viewModel.state.collectAsState()

  when (val state = clientAppState) {
    is Loading -> {
      Text(text = "Loading...")
    }
    is Success -> {
      if (state.clientApps.isEmpty()) {
        Text(text = "No apps")
      } else {
        ClientAppList(apps = state.clientApps, onAppClicked = viewModel::onAppClicked)
      }
    }
  }
}

@Composable
private fun ClientAppList(apps: List<ClientApp>, onAppClicked: (ClientApp) -> Unit) {
  LazyColumn(modifier = Modifier.fillMaxHeight()) {
    items(apps) { app ->
      // TODO Icon & package name
      Text(
        modifier = Modifier.clickable {
          onAppClicked(app)
        },
        text = "${app.packageName} : ${app.leakCount} leaks"
      )
    }
  }
}