mirror of
https://github.com/JetBrains/intellij-platform-plugin-template.git
synced 2026-01-22 16:49: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
|
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.components.service
|
||||||
import com.intellij.openapi.project.DumbAware
|
import com.intellij.openapi.project.DumbAware
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.wm.ToolWindow
|
import com.intellij.openapi.wm.ToolWindow
|
||||||
import com.intellij.openapi.wm.ToolWindowFactory
|
import com.intellij.openapi.wm.ToolWindowFactory
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import org.jetbrains.jewel.bridge.addComposeTab
|
import org.jetbrains.jewel.bridge.addComposeTab
|
||||||
|
import org.jetbrains.plugins.template.CoroutineScopeHolder
|
||||||
import org.jetbrains.plugins.template.ui.ChatAppSample
|
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.LocationsProvider
|
||||||
import org.jetbrains.plugins.template.weatherApp.services.MyLocationsViewModel
|
import org.jetbrains.plugins.template.weatherApp.services.MyLocationsViewModel
|
||||||
|
import org.jetbrains.plugins.template.weatherApp.services.WeatherForecastService
|
||||||
import org.jetbrains.plugins.template.weatherApp.ui.WeatherAppSample
|
import org.jetbrains.plugins.template.weatherApp.ui.WeatherAppSample
|
||||||
|
|
||||||
class ComposeSamplesToolWindowFactory : ToolWindowFactory, DumbAware {
|
class ComposeSamplesToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||||
|
|
||||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||||
|
val coroutineScopeHolder = project.service<CoroutineScopeHolder>()
|
||||||
|
|
||||||
toolWindow.addComposeTab("Weather App") {
|
toolWindow.addComposeTab("Weather App") {
|
||||||
val viewModel = service<MyLocationsViewModel>()
|
val locationProviderApi = remember { service<LocationsProvider>() }
|
||||||
val locationProviderApi = 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(
|
WeatherAppSample(
|
||||||
viewModel,
|
viewModel,
|
||||||
viewModel,
|
viewModel,
|
||||||
locationProviderApi
|
locationProviderApi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolWindow.addComposeTab("Chat App") { ChatAppSample() }
|
toolWindow.addComposeTab("Chat App") { ChatAppSample() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
package org.jetbrains.plugins.template.weatherApp.services
|
package org.jetbrains.plugins.template.weatherApp.services
|
||||||
|
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.application.EDT
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.Location
|
import org.jetbrains.plugins.template.weatherApp.model.Location
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.SelectableLocation
|
import org.jetbrains.plugins.template.weatherApp.model.SelectableLocation
|
||||||
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
import org.jetbrains.plugins.template.weatherApp.model.WeatherForecastData
|
||||||
@ -38,27 +40,48 @@ interface WeatherViewModelApi {
|
|||||||
fun onReloadWeatherForecast()
|
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(myInitialLocations)
|
||||||
|
|
||||||
private val myLocations = MutableStateFlow(listOf(Location("Munich", "Germany")))
|
|
||||||
|
|
||||||
private val selectedLocationIndex = MutableStateFlow(myLocations.value.lastIndex)
|
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
|
override val myLocationsFlow: StateFlow<List<SelectableLocation>> = myLocations
|
||||||
.combine(selectedLocationIndex) { locations, selectedIndex ->
|
.combine(selectedLocationIndex) { locations, selectedIndex ->
|
||||||
locations.mapIndexed { index, location ->
|
locations.mapIndexed { index, location ->
|
||||||
SelectableLocation(location, index == selectedIndex)
|
SelectableLocation(location, index == selectedIndex)
|
||||||
}
|
}
|
||||||
}.stateIn(cs, SharingStarted.WhileSubscribed(), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||||
|
|
||||||
init {
|
|
||||||
onReloadWeatherForecast()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAddLocation(locationToAdd: Location) {
|
override fun onAddLocation(locationToAdd: Location) {
|
||||||
if (myLocations.value.contains(locationToAdd)) {
|
if (myLocations.value.contains(locationToAdd)) {
|
||||||
@ -98,6 +121,20 @@ class MyLocationsViewModel(cs: CoroutineScope) : MyLocationsViewModelApi, Weathe
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadWeatherForecast(location: Location) {
|
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