mirror of
https://github.com/JetBrains/intellij-platform-plugin-template.git
synced 2026-01-20 07:39:22 +00:00
Properly scope the coroutine scope of the WeatherAppSample
ViewModel's scope is now tied to the scope of a WeatherAppSample Composable. Once WeatherAppSample Composable exits the composition tree, the used view model coroutine scope will be disposed.
This commit is contained in:
parent
85f3a32137
commit
c137351389
@ -1,28 +1,52 @@
|
||||
package org.jetbrains.plugins.template.toolWindow
|
||||
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import org.jetbrains.jewel.bridge.addComposeTab
|
||||
import org.jetbrains.plugins.template.CoroutineScopeHolder
|
||||
import org.jetbrains.plugins.template.ui.ChatAppSample
|
||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||
import org.jetbrains.plugins.template.weatherApp.services.LocationsProvider
|
||||
import org.jetbrains.plugins.template.weatherApp.services.MyLocationsViewModel
|
||||
import org.jetbrains.plugins.template.weatherApp.services.WeatherForecastService
|
||||
import org.jetbrains.plugins.template.weatherApp.ui.WeatherAppSample
|
||||
|
||||
class ComposeSamplesToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
val coroutineScopeHolder = project.service<CoroutineScopeHolder>()
|
||||
|
||||
toolWindow.addComposeTab("Weather App") {
|
||||
val viewModel = service<MyLocationsViewModel>()
|
||||
val locationProviderApi = service<LocationsProvider>()
|
||||
val locationProviderApi = remember { service<LocationsProvider>() }
|
||||
val viewModel = remember {
|
||||
val weatherForecastServiceApi = WeatherForecastService(Dispatchers.IO)
|
||||
MyLocationsViewModel(
|
||||
listOf(Location("Munich", "Germany")),
|
||||
coroutineScopeHolder
|
||||
.createScope(MyLocationsViewModel::class.java.simpleName),
|
||||
weatherForecastServiceApi
|
||||
)
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
viewModel.onReloadWeatherForecast()
|
||||
|
||||
onDispose { viewModel.cancel() }
|
||||
}
|
||||
|
||||
WeatherAppSample(
|
||||
viewModel,
|
||||
viewModel,
|
||||
locationProviderApi
|
||||
)
|
||||
}
|
||||
|
||||
toolWindow.addComposeTab("Chat App") { ChatAppSample() }
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package org.jetbrains.plugins.template.weatherApp.services
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.application.EDT
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||
import org.jetbrains.plugins.template.weatherApp.model.SelectableLocation
|
||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
||||
@ -38,27 +40,48 @@ interface WeatherViewModelApi {
|
||||
fun onReloadWeatherForecast()
|
||||
}
|
||||
|
||||
@Service
|
||||
class MyLocationsViewModel(cs: CoroutineScope) : MyLocationsViewModelApi, WeatherViewModelApi {
|
||||
/**
|
||||
* A ViewModel responsible for managing the user's locations and corresponding weather data.
|
||||
*
|
||||
* This class coordinates the interaction between the UI, locations, and weather data. It provides
|
||||
* functionality to add, delete, select locations, and reload weather forecasts. Additionally, it
|
||||
* supplies observable state flows for the list of selectable locations and the currently selected
|
||||
* location's weather forecast.
|
||||
*
|
||||
* @property myInitialLocations The initial list of user-defined locations.
|
||||
* @property viewModelScope The coroutine scope in which this ViewModel operates.
|
||||
* @property weatherService The service responsible for fetching weather forecasts for given locations.
|
||||
*/
|
||||
class MyLocationsViewModel(
|
||||
myInitialLocations: List<Location>,
|
||||
private val viewModelScope: CoroutineScope,
|
||||
private val weatherService: WeatherForecastServiceApi,
|
||||
) : MyLocationsViewModelApi, WeatherViewModelApi {
|
||||
|
||||
private val weatherService = service<WeatherForecastService>()
|
||||
|
||||
private val myLocations = MutableStateFlow(listOf(Location("Munich", "Germany")))
|
||||
private val myLocations = MutableStateFlow(myInitialLocations)
|
||||
|
||||
private val selectedLocationIndex = MutableStateFlow(myLocations.value.lastIndex)
|
||||
|
||||
override val weatherForecast: Flow<WeatherForecastData> = weatherService.weatherForecast
|
||||
private val _weatherForecast = MutableStateFlow(WeatherForecastData.EMPTY)
|
||||
|
||||
/**
|
||||
* A stream of weather forecast data that emits updates whenever the forecast changes.
|
||||
*
|
||||
* This property exposes a Flow of [WeatherForecastData], which allows consumers to observe
|
||||
* the weather forecast information for a selected location.
|
||||
*/
|
||||
override val weatherForecast: Flow<WeatherForecastData> = _weatherForecast.asStateFlow()
|
||||
|
||||
/**
|
||||
* A [StateFlow] that emits a list of [SelectableLocation] objects representing the user's
|
||||
* current locations along with the selection state of each location.
|
||||
*/
|
||||
override val myLocationsFlow: StateFlow<List<SelectableLocation>> = myLocations
|
||||
.combine(selectedLocationIndex) { locations, selectedIndex ->
|
||||
locations.mapIndexed { index, location ->
|
||||
SelectableLocation(location, index == selectedIndex)
|
||||
}
|
||||
}.stateIn(cs, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
init {
|
||||
onReloadWeatherForecast()
|
||||
}
|
||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
override fun onAddLocation(locationToAdd: Location) {
|
||||
if (myLocations.value.contains(locationToAdd)) {
|
||||
@ -98,6 +121,20 @@ class MyLocationsViewModel(cs: CoroutineScope) : MyLocationsViewModelApi, Weathe
|
||||
}
|
||||
|
||||
override fun onLoadWeatherForecast(location: Location) {
|
||||
weatherService.loadWeatherForecastFor(location)
|
||||
viewModelScope.launch {
|
||||
val weatherForecastData = weatherService.loadWeatherForecastFor(location).getOrNull() ?: return@launch
|
||||
|
||||
_weatherForecast.value = weatherForecastData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all coroutines running within the context of the ViewModel's scope.
|
||||
*
|
||||
* This method is used to release resources and stop ongoing tasks when the ViewModel
|
||||
* is no longer needed, ensuring proper cleanup of coroutine-based operations.
|
||||
*/
|
||||
fun cancel() {
|
||||
viewModelScope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user