Jede Schicht hat eine Aufgabe und kennt nur die Schicht darunter.
Der Screen ist immer in genau einem von drei Zuständen.
sealed class WeatherUiState {
const WeatherUiState();
}
class WeatherLoading extends WeatherUiState { /* ... */ }
class WeatherSuccess extends WeatherUiState {
const WeatherSuccess(this.city, this.weather);
final City city;
final CurrentWeather weather;
}
class WeatherError extends WeatherUiState { /* final String message */ }Das ViewModel hält den Zustand und meldet Änderungen mit notifyListeners().
class WeatherViewModel extends ChangeNotifier {
final WeatherUiState _uiState = WeatherSuccess(
fakeCities.first,
fakeCurrentWeather[fakeCities.first.name]!,
);
WeatherUiState get uiState => _uiState;
}Ein Widget besitzt das ViewModel, ein zweites zeigt den Zustand nur an.
class _WeatherScreenState extends State<WeatherScreen> { // stateful
final _viewModel = WeatherViewModel();
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: _viewModel,
builder: (context, _) => WeatherContent(uiState: _viewModel.uiState),
);
}
}
class WeatherContent extends StatelessWidget { /* ... */ } // statelessDas Repository liefert die Daten. Das ViewModel kennt nur das Interface.
abstract interface class WeatherRepository {
Future<CurrentWeather> getCurrentWeather(City city);
}class FakeWeatherRepository implements WeatherRepository {
FakeWeatherRepository({this.shouldFail = false});
final bool shouldFail;
@override
Future<CurrentWeather> getCurrentWeather(City city) async {
await Future.delayed(const Duration(milliseconds: 1500));
if (shouldFail) throw Exception('Keine Verbindung (simuliert)');
final weather = fakeCurrentWeather[city.name];
if (weather == null) throw Exception('Keine Wetterdaten für ${city.name}');
return weather;
}
}Das ViewModel setzt den Zustand vor und nach dem Warten und meldet jede Änderung.
Future<void> loadWeather(City city) async {
_uiState = const WeatherLoading();
notifyListeners();
try {
final weather = await _repository.getCurrentWeather(city);
_uiState = WeatherSuccess(city, weather);
} catch (e) {
_uiState = WeatherError('$e');
}
notifyListeners();
}Das ViewModel bekommt sein Repository als optionalen Parameter. Für die echte API ändert sich später nur diese Zeile.
WeatherViewModel({WeatherRepository? repository})
: _repository = repository ?? FakeWeatherRepository();