diff --git a/build/testfile.dill b/build/testfile.dill
deleted file mode 100644
index 713c092..0000000
Binary files a/build/testfile.dill and /dev/null differ
diff --git a/build/testfile.dill.track.dill b/build/testfile.dill.track.dill
deleted file mode 100644
index 6105ec0..0000000
Binary files a/build/testfile.dill.track.dill and /dev/null differ
diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml
index 3b19a8a..068d682 100644
--- a/example/analysis_options.yaml
+++ b/example/analysis_options.yaml
@@ -6,7 +6,8 @@ analyzer:
unused_local_variable: error
dead_code: error
exclude:
- - example/lib/json/weather_in_cities.g.dart
+ - lib/service/json/weather_in_cities.g.dart
+
linter:
rules:
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 2603af2..6363669 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -22,7 +22,6 @@ android {
}
defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.flutterweatherdemo"
minSdkVersion 16
targetSdkVersion 27
@@ -33,8 +32,6 @@ android {
buildTypes {
release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
diff --git a/example/ios/Flutter/flutter_export_environment.sh b/example/ios/Flutter/flutter_export_environment.sh
index d41505d..44ffa37 100755
--- a/example/ios/Flutter/flutter_export_environment.sh
+++ b/example/ios/Flutter/flutter_export_environment.sh
@@ -2,14 +2,13 @@
# This is a generated file; do not edit or check into version control.
export "FLUTTER_ROOT=/Users/oleksandr.babich/development/flutter"
export "FLUTTER_APPLICATION_PATH=/Users/oleksandr.babich/development/rx/rx_widgets/example"
-export "FLUTTER_TARGET=/Users/oleksandr.babich/development/rx/rx_widgets/example/lib/main.dart"
+export "COCOAPODS_PARALLEL_CODE_SIGN=true"
+export "FLUTTER_TARGET=lib/main.dart"
export "FLUTTER_BUILD_DIR=build"
export "SYMROOT=${SOURCE_ROOT}/../build/ios"
-export "OTHER_LDFLAGS=$(inherited) -framework Flutter"
-export "FLUTTER_FRAMEWORK_DIR=/Users/oleksandr.babich/development/flutter/bin/cache/artifacts/engine/ios"
export "FLUTTER_BUILD_NAME=1.0.0"
export "FLUTTER_BUILD_NUMBER=1"
export "DART_OBFUSCATION=false"
-export "TRACK_WIDGET_CREATION=true"
+export "TRACK_WIDGET_CREATION=false"
export "TREE_SHAKE_ICONS=false"
export "PACKAGE_CONFIG=.packages"
diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
index 59c6d39..919434a 100644
--- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -1,7 +1,7 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/example/lib/homepage/home_page.dart b/example/lib/homepage/home_page.dart
new file mode 100644
index 0000000..73df03a
--- /dev/null
+++ b/example/lib/homepage/home_page.dart
@@ -0,0 +1,156 @@
+import 'package:flutter/material.dart';
+import 'package:rx_widget_demo/homepage/weather_list_view.dart';
+import 'package:rx_widget_demo/keys.dart';
+import 'package:rx_widget_demo/model_provider.dart';
+import 'package:rx_widget_demo/service/weather_entry.dart';
+import 'package:rx_widgets/rx_widgets.dart';
+
+// ignore_for_file: deprecated_member_use
+
+const noResultsText = 'No matching city data found. Try refining search.';
+
+class HomePage extends StatelessWidget {
+ final TextEditingController _controller = TextEditingController();
+
+ HomePage({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final homePageModel = ModelProvider.of(context);
+ /*return Scaffold(
+ appBar: AppBar(title: AppBar(title: Text('WeatherDemo'))),
+ resizeToAvoidBottomInset: false,
+ body: SafeArea(
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: TextField(
+ key: AppKeys.textField,
+ autocorrect: false,
+ controller: _controller,
+ decoration: InputDecoration(
+ hintText: "Filter cities",
+ ),
+ style: TextStyle(
+ fontSize: 20.0,
+ ),
+ onChanged: (s) => homePageModel.textChangedCommand(s),
+ // onChanged: ModelProvider.of(context).textChangedCommand,
+ ),
+ ),
+ Text('My Message'),
+ ],
+ ),
+ ),
+ );*/
+ return Scaffold(
+ appBar: AppBar(title: Text("WeatherDemo")),
+ resizeToAvoidBottomInset: false,
+ body: SafeArea(
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: TextField(
+ key: AppKeys.textField,
+ autocorrect: false,
+ controller: _controller,
+ decoration: InputDecoration(hintText: "Filter cities"),
+ style: TextStyle(
+ fontSize: 20.0,
+ ),
+ onChanged: (s) => homePageModel.textChangedCommand(s),
+ ),
+ ),
+ Expanded(
+ child: RxLoader>(
+ spinnerKey: AppKeys.loadingSpinner,
+ radius: 25.0,
+ commandResults: homePageModel.updateWeatherCommand.results,
+ dataBuilder: (_, data) {
+ if (data.isEmpty) return Center(child: Text(noResultsText));
+ return WeatherListView(data, key: AppKeys.weatherList);
+ },
+ placeHolderBuilder: (_) => Center(
+ key: AppKeys.loaderPlaceHolder, child: Text("No Data")),
+ errorBuilder: (_, ex) => Center(
+ key: AppKeys.loaderError,
+ child: Text("Error: ${ex.toString()}")),
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: Row(
+ children: [
+ Expanded(
+ // This might be solved with a StreamBuilder to but it should show `WidgetSelector`
+ child: WidgetSelector(
+ key: AppKeys.widgetSelector,
+ buildEvents:
+ homePageModel.updateWeatherCommand.canExecute,
+ //We access our ViewModel through the inherited Widget
+ onTrue: RaisedButton(
+ key: AppKeys.updateButtonEnabled,
+ child: Text("Update"),
+ onPressed: () {
+ _controller.clear();
+ homePageModel.updateWeatherCommand('');
+ },
+ ),
+ onFalse: RaisedButton(
+ key: AppKeys.updateButtonDisabled,
+ child: Text("Please Wait"),
+ onPressed: null,
+ ),
+ ),
+ ),
+ StateFullSwitch(
+ state: true,
+ onChanged: (b) => homePageModel.switchChangedCommand(b),
+ // onChanged: homePageModel.switchChangedCommand,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+/// As the normal switch does not even remember and display its current state
+/// we us this one
+class StateFullSwitch extends StatefulWidget {
+ final bool state;
+ final ValueChanged onChanged;
+
+ StateFullSwitch({required this.state, required this.onChanged});
+
+ @override
+ StateFullSwitchState createState() {
+ return StateFullSwitchState(state, onChanged);
+ }
+}
+
+class StateFullSwitchState extends State {
+ bool state;
+ ValueChanged handler;
+
+ StateFullSwitchState(this.state, this.handler);
+
+ @override
+ Widget build(BuildContext context) {
+ return Switch(
+ key: AppKeys.updateSwitch,
+ value: state,
+ onChanged: (b) {
+ setState(() => state = b);
+ handler(b);
+ },
+ );
+ }
+}
diff --git a/example/lib/homepage/homepage.dart b/example/lib/homepage/homepage.dart
deleted file mode 100644
index 5fe99ab..0000000
--- a/example/lib/homepage/homepage.dart
+++ /dev/null
@@ -1,120 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:rx_widget_demo/homepage/weather_list_view.dart';
-import 'package:rx_widget_demo/keys.dart';
-import 'package:rx_widget_demo/model_provider.dart';
-import 'package:rx_widgets/rx_widgets.dart';
-
-import 'package:rx_widget_demo/service/weather_entry.dart';
-
-class HomePage extends StatefulWidget {
- @override
- HomePageState createState() {
- return HomePageState();
- }
-}
-
-class HomePageState extends State {
- final TextEditingController _controller = TextEditingController();
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: Text("WeatherDemo")),
- resizeToAvoidBottomPadding: false,
- body: Column(
- children: [
- Padding(
- padding: const EdgeInsets.all(16.0),
- child: TextField(
- key: AppKeys.textField,
- autocorrect: false,
- controller: _controller,
- decoration: InputDecoration(
- hintText: "Filter cities",
- ),
- style: TextStyle(
- fontSize: 20.0,
- ),
- onChanged: ModelProvider.of(context).textChangedCommand,
- ),
- ),
- Expanded(
- child: RxLoader>(
- spinnerKey: AppKeys.loadingSpinner,
- radius: 25.0,
- commandResults: ModelProvider.of(context).updateWeatherCommand.results,
- dataBuilder: (context, data) => WeatherListView(data, key: AppKeys.weatherList),
- placeHolderBuilder: (context) => Center(key: AppKeys.loaderPlaceHolder, child: Text("No Data")),
- errorBuilder: (context, ex) => Center(key: AppKeys.loaderError, child: Text("Error: ${ex.toString()}")),
- ),
- ),
- Padding(
- padding: const EdgeInsets.all(8.0),
- child: Row(
- children: [
- Expanded(
- // This might be solved with a StreamBuilder to but it should show `WidgetSelector`
- child: WidgetSelector(
- buildEvents: ModelProvider.of(context)
- .updateWeatherCommand
- .canExecute, //We access our ViewModel through the inherited Widget
- onTrue: RaisedButton(
- key: AppKeys.updateButtonEnabled,
- child: Text("Update"),
- onPressed: () {
- _controller.clear();
- ModelProvider.of(context).updateWeatherCommand();
- },
- ),
- onFalse: RaisedButton(
- key: AppKeys.updateButtonDisabled,
- child: Text("Please Wait"),
- onPressed: null,
- ),
- ),
- ),
- StateFullSwitch(
- state: true,
- onChanged: ModelProvider.of(context).switchChangedCommand,
- )
- ],
- ),
- ),
- ],
- ),
- );
- }
-}
-
-/// As the normal switch does not even remember and display its current state
-/// we us this one
-class StateFullSwitch extends StatefulWidget {
- final bool state;
- final ValueChanged onChanged;
-
- StateFullSwitch({this.state, this.onChanged});
-
- @override
- StateFullSwitchState createState() {
- return StateFullSwitchState(state, onChanged);
- }
-}
-
-class StateFullSwitchState extends State {
- bool state;
- ValueChanged handler;
-
- StateFullSwitchState(this.state, this.handler);
-
- @override
- Widget build(BuildContext context) {
- return Switch(
- key: AppKeys.updateSwitch,
- value: state,
- onChanged: (b) {
- setState(() => state = b);
- handler(b);
- },
- );
- }
-}
diff --git a/example/lib/homepage/homepage_model.dart b/example/lib/homepage/homepage_model.dart
index d19ce33..6df5177 100644
--- a/example/lib/homepage/homepage_model.dart
+++ b/example/lib/homepage/homepage_model.dart
@@ -1,8 +1,7 @@
-
import 'package:rx_command/rx_command.dart';
-import 'package:rxdart/rxdart.dart';
-import 'package:rx_widget_demo/service/weather_service.dart';
import 'package:rx_widget_demo/service/weather_entry.dart';
+import 'package:rx_widget_demo/service/weather_service.dart';
+import 'package:rxdart/rxdart.dart';
class HomePageModel {
final WeatherService service;
@@ -17,7 +16,7 @@ class HomePageModel {
this.service,
);
- factory HomePageModel(WeatherService service ) {
+ factory HomePageModel(WeatherService service) {
// Command expects a bool value when executed and issues the value on it's
// result Observable (stream)
final _switchChangedCommand = RxCommand.createSync((b) => b);
@@ -26,7 +25,9 @@ class HomePageModel {
// the updateWeatherCommand
final _updateWeatherCommand =
RxCommand.createAsync>(
- service.getWeatherEntriesForCity, canExecute: _switchChangedCommand);
+ (city) => service.getWeatherEntriesForCity(city),
+ restriction: _switchChangedCommand,
+ );
// Will be called on every change of the search field
final _textChangedCommand = RxCommand.createSync((s) => s);
@@ -34,14 +35,14 @@ class HomePageModel {
// When the user starts typing
_textChangedCommand
// Wait for the user to stop typing for 500ms
- .debounceTime( Duration(milliseconds: 500))
+ .debounceTime(Duration(milliseconds: 500))
// Then call the updateWeatherCommand
.listen(_updateWeatherCommand);
// Update data on startup
_updateWeatherCommand('');
- return HomePageModel._(
+ return HomePageModel._(
_updateWeatherCommand,
_switchChangedCommand,
_textChangedCommand,
diff --git a/example/lib/homepage/weather_list_view.dart b/example/lib/homepage/weather_list_view.dart
index ee51f16..c9e2790 100644
--- a/example/lib/homepage/weather_list_view.dart
+++ b/example/lib/homepage/weather_list_view.dart
@@ -4,39 +4,43 @@ import 'package:rx_widget_demo/keys.dart';
import 'package:rx_widget_demo/service/weather_entry.dart';
import 'package:rx_widget_demo/weather_icons.dart';
-
class WeatherListView extends StatelessWidget {
-
final List data;
-
- WeatherListView(this.data, {Key key}) : super(key: key);
+ WeatherListView(this.data, {Key? key}) : super(key: key);
-
@override
Widget build(BuildContext context) {
return ListView.builder(
- key: AppKeys.cityList,
- itemCount: data.length,
- itemBuilder: (BuildContext context, int index) =>
- WeatherItem(entry: data[index]),
- );
- }
+ key: AppKeys.cityList,
+ itemCount: data.length,
+ itemBuilder: (BuildContext context, int index) =>
+ WeatherItem(entry: data[index]),
+ );
+ }
}
class WeatherItem extends StatelessWidget {
final WeatherEntry entry;
- WeatherItem({Key key, @required this.entry}) : super(key: key);
+ WeatherItem({Key? key, required this.entry}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
- leading: Icon(_weatherIdToIcon(entry.weatherId), size: 28.0,),
- title: Text(entry.cityName),
- subtitle: Text(entry.description, style: TextStyle(fontStyle: FontStyle.italic),),
- trailing: Text('${entry.temperature.round()} °', style: TextStyle(fontSize: 20.0),
- ),
+ leading: Icon(
+ _weatherIdToIcon(entry.weatherId),
+ size: 28.0,
+ ),
+ title: Text(entry.cityName),
+ subtitle: Text(
+ entry.description,
+ style: TextStyle(fontStyle: FontStyle.italic),
+ ),
+ trailing: Text(
+ '${entry.temperature.round()} °',
+ style: TextStyle(fontSize: 20.0),
+ ),
);
}
diff --git a/example/lib/keys.dart b/example/lib/keys.dart
index c4b5247..1df9f4c 100644
--- a/example/lib/keys.dart
+++ b/example/lib/keys.dart
@@ -9,6 +9,7 @@ class AppKeys {
static final Key loadingSpinner = new Key('loadingSpinner');
static final Key updateButtonEnabled = new Key('updateButtonEnabled');
static final Key updateButtonDisabled = new Key('updateButtonDisabled');
+ static final Key widgetSelector = new Key('widgetSelector');
static final Key loaderPlaceHolder = new Key('loaderPlaceHolder');
static final Key loaderError = new Key('loaderError');
static final Key updateSwitch = new Key('updateSwitch');
diff --git a/example/lib/main.dart b/example/lib/main.dart
index 504fae7..d245f7d 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,24 +1,23 @@
import 'package:flutter/material.dart';
-
-import 'package:rx_widget_demo/homepage/homepage.dart';
+import 'package:http/http.dart' as http;
+import 'package:rx_widget_demo/homepage/home_page.dart';
import 'package:rx_widget_demo/homepage/homepage_model.dart';
import 'package:rx_widget_demo/model_provider.dart';
import 'package:rx_widget_demo/service/weather_service.dart';
-import 'package:http/http.dart' as http;
void main() {
final weatherService = WeatherService(http.Client());
final homePageModel = HomePageModel(weatherService);
- runApp(MyApp(
- model: homePageModel,
- ));
+ runApp(
+ MyApp(model: homePageModel),
+ );
}
class MyApp extends StatelessWidget {
final HomePageModel model;
- const MyApp({Key key, this.model}) : super(key: key);
+ const MyApp({Key? key, required this.model}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -37,4 +36,4 @@ class MyApp extends StatelessWidget {
),
);
}
-}
\ No newline at end of file
+}
diff --git a/example/lib/model_provider.dart b/example/lib/model_provider.dart
index b05d2c1..be02488 100644
--- a/example/lib/model_provider.dart
+++ b/example/lib/model_provider.dart
@@ -2,20 +2,20 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:rx_widget_demo/homepage/homepage_model.dart';
-
// InheritedWidgets allow you to propagate values down the Widget Tree.
// it can then be accessed by just writing TheViewModel.of(context)
class ModelProvider extends InheritedWidget {
final HomePageModel model;
- const ModelProvider({Key key, @required this.model, @required Widget child})
- : assert(model != null),
- assert(child != null),
- super(key: key, child: child);
+ const ModelProvider({Key? key, required this.model, required Widget child})
+ : super(key: key, child: child);
- static HomePageModel of(BuildContext context) =>
- (context.dependOnInheritedWidgetOfExactType())
- .model;
+ static HomePageModel of(BuildContext context) {
+ final type = context.dependOnInheritedWidgetOfExactType();
+ final model = type?.model;
+ assert(model != null);
+ return model!;
+ }
@override
bool updateShouldNotify(ModelProvider oldWidget) => model != oldWidget.model;
diff --git a/example/lib/service/json/weather_in_cities.dart b/example/lib/service/json/weather_in_cities.dart
index f139e16..86d46cf 100644
--- a/example/lib/service/json/weather_in_cities.dart
+++ b/example/lib/service/json/weather_in_cities.dart
@@ -5,7 +5,7 @@ import "package:json_annotation/json_annotation.dart";
part "weather_in_cities.g.dart";
@JsonSerializable()
-class WeatherInCities extends Object with _$WeatherInCitiesSerializerMixin {
+class WeatherInCities {
WeatherInCities(this.cnt, this.calctime, this.cod, this.cities);
@JsonKey(name: 'cnt')
@@ -25,7 +25,7 @@ class WeatherInCities extends Object with _$WeatherInCitiesSerializerMixin {
}
@JsonSerializable()
-class City extends Object with _$CitySerializerMixin {
+class City {
City(this.id, this.coord, this.clouds, this.dt, this.name, this.main,
this.rain, this.weather, this.wind);
@@ -48,7 +48,7 @@ class City extends Object with _$CitySerializerMixin {
final Main main;
@JsonKey(name: 'rain')
- final Rain rain;
+ final Rain? rain;
@JsonKey(name: 'weather')
final List weather;
@@ -60,7 +60,7 @@ class City extends Object with _$CitySerializerMixin {
}
@JsonSerializable()
-class Coord extends Object with _$CoordSerializerMixin {
+class Coord {
Coord(this.lat, this.lon);
@JsonKey(name: 'Lat')
@@ -73,7 +73,7 @@ class Coord extends Object with _$CoordSerializerMixin {
}
@JsonSerializable()
-class Clouds extends Object with _$CloudsSerializerMixin {
+class Clouds {
Clouds(this.today);
@JsonKey(name: 'today')
@@ -83,18 +83,18 @@ class Clouds extends Object with _$CloudsSerializerMixin {
}
@JsonSerializable()
-class Main extends Object with _$MainSerializerMixin {
+class Main {
Main(this.seaLevel, this.humidity, this.grndLevel, this.pressure,
this.tempMax, this.temp, this.tempMin);
- @JsonKey(name: 'sea_level', nullable: true)
- final double seaLevel;
+ @JsonKey(name: 'sea_level')
+ final double? seaLevel;
@JsonKey(name: 'humidity')
final int humidity;
- @JsonKey(name: 'grnd_level', nullable: true)
- final double grndLevel;
+ @JsonKey(name: 'grnd_level')
+ final double? grndLevel;
@JsonKey(name: 'pressure')
final double pressure;
@@ -112,7 +112,7 @@ class Main extends Object with _$MainSerializerMixin {
}
@JsonSerializable()
-class Rain extends Object with _$RainSerializerMixin {
+class Rain {
Rain(this.threeH);
@JsonKey(name: '3h')
@@ -122,7 +122,7 @@ class Rain extends Object with _$RainSerializerMixin {
}
@JsonSerializable()
-class Weather extends Object with _$WeatherSerializerMixin {
+class Weather {
Weather(this.icon, this.description, this.id, this.main);
@JsonKey(name: 'icon')
@@ -142,7 +142,7 @@ class Weather extends Object with _$WeatherSerializerMixin {
}
@JsonSerializable()
-class Wind extends Object with _$WindSerializerMixin {
+class Wind {
Wind(this.deg, this.speed);
@JsonKey(name: 'deg')
diff --git a/example/lib/service/json/weather_in_cities.g.dart b/example/lib/service/json/weather_in_cities.g.dart
index 3a602f5..a50731e 100644
--- a/example/lib/service/json/weather_in_cities.g.dart
+++ b/example/lib/service/json/weather_in_cities.g.dart
@@ -3,187 +3,136 @@
part of 'weather_in_cities.dart';
// **************************************************************************
-// Generator: JsonSerializableGenerator
+// JsonSerializableGenerator
// **************************************************************************
-WeatherInCities _$WeatherInCitiesFromJson(Map json) =>
- new WeatherInCities(
- json['cnt'] as int,
- (json['calctime'] as num)?.toDouble(),
- json['cod'] as int,
- (json['list'] as List)
- ?.map((e) =>
- e == null ? null : new City.fromJson(e as Map))
- ?.toList());
-
-abstract class _$WeatherInCitiesSerializerMixin {
- int get cnt;
-
- double get calctime;
-
- int get cod;
-
- List get cities;
-
- Map toJson() => {
- 'cnt': cnt,
- 'calctime': calctime,
- 'cod': cod,
- 'list': cities
- };
+WeatherInCities _$WeatherInCitiesFromJson(Map json) {
+ return WeatherInCities(
+ json['cnt'] as int,
+ (json['calctime'] as num).toDouble(),
+ json['cod'] as int,
+ (json['list'] as List)
+ .map((e) => City.fromJson(e as Map))
+ .toList(),
+ );
}
-City _$CityFromJson(Map json) => new City(
+Map _$WeatherInCitiesToJson(WeatherInCities instance) =>
+ {
+ 'cnt': instance.cnt,
+ 'calctime': instance.calctime,
+ 'cod': instance.cod,
+ 'list': instance.cities,
+ };
+
+City _$CityFromJson(Map json) {
+ return City(
json['id'] as int,
- json['coord'] == null
- ? null
- : new Coord.fromJson(json['coord'] as Map),
- json['clouds'] == null
- ? null
- : new Clouds.fromJson(json['clouds'] as Map),
+ Coord.fromJson(json['coord'] as Map),
+ Clouds.fromJson(json['clouds'] as Map),
json['dt'] as int,
json['name'] as String,
- json['main'] == null
- ? null
- : new Main.fromJson(json['main'] as Map),
+ Main.fromJson(json['main'] as Map),
json['rain'] == null
? null
- : new Rain.fromJson(json['rain'] as Map),
- (json['weather'] as List)
- ?.map((e) =>
- e == null ? null : new Weather.fromJson(e as Map))
- ?.toList(),
- json['wind'] == null
- ? null
- : new Wind.fromJson(json['wind'] as Map));
-
-abstract class _$CitySerializerMixin {
- int get id;
-
- Coord get coord;
-
- Clouds get clouds;
-
- int get dt;
-
- String get name;
-
- Main get main;
-
- Rain get rain;
-
- List get weather;
-
- Wind get wind;
-
- Map toJson() => {
- 'id': id,
- 'coord': coord,
- 'clouds': clouds,
- 'dt': dt,
- 'name': name,
- 'main': main,
- 'rain': rain,
- 'weather': weather,
- 'wind': wind
- };
+ : Rain.fromJson(json['rain'] as Map),
+ (json['weather'] as List)
+ .map((e) => Weather.fromJson(e as Map))
+ .toList(),
+ Wind.fromJson(json['wind'] as Map),
+ );
}
-Coord _$CoordFromJson(Map json) => new Coord(
- (json['Lat'] as num)?.toDouble(), (json['Lon'] as num)?.toDouble());
-
-abstract class _$CoordSerializerMixin {
- double get lat;
-
- double get lon;
-
- Map toJson() => {'Lat': lat, 'Lon': lon};
+Map _$CityToJson(City instance) => {
+ 'id': instance.id,
+ 'coord': instance.coord,
+ 'clouds': instance.clouds,
+ 'dt': instance.dt,
+ 'name': instance.name,
+ 'main': instance.main,
+ 'rain': instance.rain,
+ 'weather': instance.weather,
+ 'wind': instance.wind,
+ };
+
+Coord _$CoordFromJson(Map json) {
+ return Coord(
+ (json['Lat'] as num).toDouble(),
+ (json['Lon'] as num).toDouble(),
+ );
}
-Clouds _$CloudsFromJson(Map json) =>
- new Clouds(json['today'] as int);
+Map _$CoordToJson(Coord instance) => {
+ 'Lat': instance.lat,
+ 'Lon': instance.lon,
+ };
-abstract class _$CloudsSerializerMixin {
- int get today;
-
- Map toJson() => {'today': today};
+Clouds _$CloudsFromJson(Map json) {
+ return Clouds(
+ json['today'] as int,
+ );
}
-Main _$MainFromJson(Map json) => new Main(
- (json['sea_level'] as num)?.toDouble(),
- json['humidity'] as int,
- (json['grnd_level'] as num)?.toDouble(),
- (json['pressure'] as num)?.toDouble(),
- (json['temp_max'] as num)?.toDouble(),
- (json['temp'] as num)?.toDouble(),
- (json['temp_min'] as num)?.toDouble());
-
-abstract class _$MainSerializerMixin {
- double get seaLevel;
-
- int get humidity;
-
- double get grndLevel;
-
- double get pressure;
+Map _$CloudsToJson(Clouds instance) => {
+ 'today': instance.today,
+ };
- double get tempMax;
-
- double get temp;
-
- double get tempMin;
-
- Map toJson() => {
- 'sea_level': seaLevel,
- 'humidity': humidity,
- 'grnd_level': grndLevel,
- 'pressure': pressure,
- 'temp_max': tempMax,
- 'temp': temp,
- 'temp_min': tempMin
- };
+Main _$MainFromJson(Map json) {
+ return Main(
+ (json['sea_level'] as num?)?.toDouble(),
+ json['humidity'] as int,
+ (json['grnd_level'] as num?)?.toDouble(),
+ (json['pressure'] as num).toDouble(),
+ (json['temp_max'] as num).toDouble(),
+ (json['temp'] as num).toDouble(),
+ (json['temp_min'] as num).toDouble(),
+ );
}
-Rain _$RainFromJson(Map json) =>
- new Rain((json['3h'] as num)?.toDouble());
-
-abstract class _$RainSerializerMixin {
- double get threeH;
-
- Map toJson() => {'3h': threeH};
+Map _$MainToJson(Main instance) => {
+ 'sea_level': instance.seaLevel,
+ 'humidity': instance.humidity,
+ 'grnd_level': instance.grndLevel,
+ 'pressure': instance.pressure,
+ 'temp_max': instance.tempMax,
+ 'temp': instance.temp,
+ 'temp_min': instance.tempMin,
+ };
+
+Rain _$RainFromJson(Map json) {
+ return Rain(
+ (json['3h'] as num).toDouble(),
+ );
}
-Weather _$WeatherFromJson(Map json) => new Weather(
+Map _$RainToJson(Rain instance) => {
+ '3h': instance.threeH,
+ };
+
+Weather _$WeatherFromJson(Map json) {
+ return Weather(
json['icon'] as String,
json['description'] as String,
json['id'] as int,
- json['main'] as String);
-
-abstract class _$WeatherSerializerMixin {
- String get icon;
-
- String get description;
-
- int get id;
-
- String get main;
-
- Map toJson() => {
- 'icon': icon,
- 'description': description,
- 'id': id,
- 'main': main
- };
+ json['main'] as String,
+ );
}
-Wind _$WindFromJson(Map json) => new Wind(
- (json['deg'] as num)?.toDouble(), (json['speed'] as num)?.toDouble());
-
-abstract class _$WindSerializerMixin {
- double get deg;
-
- double get speed;
-
- Map toJson() =>
- {'deg': deg, 'speed': speed};
+Map _$WeatherToJson(Weather instance) => {
+ 'icon': instance.icon,
+ 'description': instance.description,
+ 'id': instance.id,
+ 'main': instance.main,
+ };
+
+Wind _$WindFromJson(Map json) {
+ return Wind(
+ (json['deg'] as num).toDouble(),
+ (json['speed'] as num).toDouble(),
+ );
}
+
+Map _$WindToJson(Wind instance) => {
+ 'deg': instance.deg,
+ 'speed': instance.speed,
+ };
diff --git a/example/lib/service/weather_entry.dart b/example/lib/service/weather_entry.dart
index 27a6f2e..606df84 100644
--- a/example/lib/service/weather_entry.dart
+++ b/example/lib/service/weather_entry.dart
@@ -19,14 +19,15 @@ class WeatherEntry {
: cityName = city.name,
wind = city.wind.speed,
temperature = city.main.temp,
- description = city.weather[0]?.description,
+ description = city.weather[0].description,
weatherId = city.weather[0].id;
- @override
- bool operator == (other) {
- return cityName == other.cityName;
- }
+ @override
+ bool operator ==(other) {
+ if (other is! WeatherEntry) return false;
+ return cityName == other.cityName;
+ }
- @override
- int get hashCode => cityName.hashCode;
+ @override
+ int get hashCode => cityName.hashCode;
}
diff --git a/example/lib/service/weather_service.dart b/example/lib/service/weather_service.dart
index 77eac65..55c6755 100644
--- a/example/lib/service/weather_service.dart
+++ b/example/lib/service/weather_service.dart
@@ -1,32 +1,34 @@
import 'dart:async';
import 'dart:convert';
+import 'package:http/http.dart' as http;
import 'package:rx_widget_demo/service/json/weather_in_cities.dart';
import 'package:rx_widget_demo/service/weather_entry.dart';
-import 'package:http/http.dart' as http;
class WeatherService {
- static String url =
- "http://api.openweathermap.org/data/2.5/box/city?bbox=5,47,14,54,20&appid=27ac337102cc4931c24ba0b50aca6bbd";
-
- final http.Client client;
+ static final url = 'https://api.openweathermap.org/data/2.5/box/'
+ 'city?bbox=12,32,15,37,10&appid=27ac337102cc4931c24ba0b50aca6bbd';
+ static final uri = Uri.parse(url);
WeatherService(this.client);
- Future> getWeatherEntriesForCity(String filter) async {
- final response = await client.get(url);
+ final http.Client client;
+ Future> getWeatherEntriesForCity(String? filter) async {
+ final response = await client.get(uri);
+ print(response.statusCode);
if (response.statusCode == 200) {
- return new WeatherInCities.fromJson(json.decode(response.body) as Map)
+ return WeatherInCities.fromJson(
+ json.decode(response.body) as Map)
.cities
.where((weatherInCity) =>
filter == null ||
filter.isEmpty ||
weatherInCity.name.toUpperCase().startsWith(filter.toUpperCase()))
- .map((weatherInCity) => new WeatherEntry.from(weatherInCity))
+ .map((weatherInCity) => WeatherEntry.from(weatherInCity))
.toList();
} else {
- throw new Exception('No cities found');
+ throw Exception(response.body);
}
}
}
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index b082950..223df25 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -1,73 +1,34 @@
name: rx_widget_demo
description: A new Flutter project.
+environment:
+ sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
- # The following adds the Cupertino Icons font to your application.
- # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.0
rxdart : any
json_annotation: any
- http: any
- rx_command: ^5.0.1
+ http: ^0.13.1
+ rx_command: ^6.0.0-null-safety.3
rx_widgets:
path: ../
dev_dependencies:
test: any
- mockito: any
+ mockito: ^5.0.5
quiver: any
build_runner: any
- json_serializable: any
+ json_serializable: 4.1.1
flutter_test:
sdk: flutter
-# For information on the generic Dart part of this file, see the
-# following page: https://www.dartlang.org/tools/pub/pubspec
-
-# The following section is specific to Flutter.
flutter:
-
- # The following line ensures that the Material Icons font is
- # included with your application, so that you can use the icons in
- # the material Icons class.
uses-material-design: true
fonts:
- family: WeatherIcons
fonts:
- - asset: fonts/WeatherIcons.ttf
-
- # To add assets to your application, add an assets section, like this:
- # assets:
- # - images/a_dot_burr.jpeg
- # - images/a_dot_ham.jpeg
-
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.io/assets-and-images/#resolution-aware.
-
- # For details regarding adding assets from package dependencies, see
- # https://flutter.io/assets-and-images/#from-packages
-
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.io/custom-fonts/#from-packages
+ - asset: fonts/WeatherIcons.ttf
\ No newline at end of file
diff --git a/example/test/homepage_model_test.dart b/example/test/homepage_model_test.dart
index ece98ff..8d3eeb7 100644
--- a/example/test/homepage_model_test.dart
+++ b/example/test/homepage_model_test.dart
@@ -1,4 +1,6 @@
import 'dart:async';
+
+import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:mockito/src/mock.dart';
import 'package:quiver/testing/async.dart';
@@ -8,29 +10,29 @@ import 'package:rx_widget_demo/service/weather_entry.dart';
import 'package:rx_widget_demo/service/weather_service.dart';
import 'package:test/test.dart';
-class MockService extends Mock implements WeatherService {}
+import 'homepage_model_test.mocks.dart';
+// class MockService extends Mock implements WeatherService {}
+@GenerateMocks([WeatherService])
main() {
group('HomePageModel', () {
test(
'should immediately fetch the weather with an empty string when the HomePageModel gets created',
() async {
- final service = MockService();
+ final service = MockWeatherService();
when(service.getWeatherEntriesForCity(any))
- .thenAnswer((_) => Future.sync(() => []));
- final model = HomePageModel(service); // ignore: unused_local_variable
-
-
+ .thenAnswer((_) => Future.sync(() => []));
+ final model = HomePageModel(service);
- expect(model.updateWeatherCommand.results, emits(TypeMatcher()));
+ expect(model.updateWeatherCommand.results,
+ emits(TypeMatcher()));
});
test('should not fetch if switch is off', () async {
- final service = MockService();
- final model = HomePageModel(service);
-
+ final service = MockWeatherService();
when(service.getWeatherEntriesForCity(any))
- .thenAnswer((_) => Future.sync(() => []));
+ .thenAnswer((_) => Future.sync(() => []));
+ final model = HomePageModel(service);
model.switchChangedCommand(false);
model.updateWeatherCommand('A');
@@ -39,15 +41,14 @@ main() {
test('should filter after the user stops typing for 500ms', () async {
// Use FakeAsync from the Quiver package to simulate time
- FakeAsync().run((time) {
- final service = MockService();
- final model = HomePageModel(service);
-
+ FakeAsync().run((time) {
+ final service = MockWeatherService();
when(service.getWeatherEntriesForCity(any))
- .thenAnswer((_) => Future.sync(() => []));
+ .thenAnswer((_) => Future.sync(() => []));
+ final model = HomePageModel(service);
model.textChangedCommand('A');
- time.elapse( Duration(milliseconds: 1000));
+ time.elapse(Duration(milliseconds: 1000));
verify(service.getWeatherEntriesForCity('A'));
});
@@ -55,15 +56,14 @@ main() {
test('should not search if the user has not paused for 500ms', () async {
// Use FakeAsync from the Quiver package to simulate time
- FakeAsync().run((time) {
- final service = MockService();
- final model = HomePageModel(service);
-
+ FakeAsync().run((time) {
+ final service = MockWeatherService();
when(service.getWeatherEntriesForCity(any))
- .thenAnswer((_) => Future.sync(() => []));
+ .thenAnswer((_) => Future.sync(() => []));
+ final model = HomePageModel(service);
model.textChangedCommand('A');
- time.elapse( Duration(milliseconds: 100));
+ time.elapse(Duration(milliseconds: 100));
verifyNever(service.getWeatherEntriesForCity('A'));
});
diff --git a/example/test/homepage_model_test.mocks.dart b/example/test/homepage_model_test.mocks.dart
new file mode 100644
index 0000000..69d3da4
--- /dev/null
+++ b/example/test/homepage_model_test.mocks.dart
@@ -0,0 +1,35 @@
+// Mocks generated by Mockito 5.0.5 from annotations
+// in rx_widget_demo/test/homepage_model_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i4;
+
+import 'package:http/src/client.dart' as _i2;
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:rx_widget_demo/service/weather_entry.dart' as _i5;
+import 'package:rx_widget_demo/service/weather_service.dart' as _i3;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeClient extends _i1.Fake implements _i2.Client {}
+
+/// A class which mocks [WeatherService].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockWeatherService extends _i1.Mock implements _i3.WeatherService {
+ MockWeatherService() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.Client get client => (super.noSuchMethod(Invocation.getter(#client),
+ returnValue: _FakeClient()) as _i2.Client);
+ @override
+ _i4.Future> getWeatherEntriesForCity(String? filter) =>
+ (super.noSuchMethod(
+ Invocation.method(#getWeatherEntriesForCity, [filter]),
+ returnValue:
+ Future>.value(<_i5.WeatherEntry>[]))
+ as _i4.Future>);
+}
diff --git a/example/test/homepage_test.dart b/example/test/homepage_test.dart
index 0958142..a7ef869 100644
--- a/example/test/homepage_test.dart
+++ b/example/test/homepage_test.dart
@@ -1,56 +1,63 @@
import 'dart:async';
+import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:rx_command/rx_command.dart';
-import 'package:rx_widget_demo/homepage/homepage.dart';
+import 'package:rx_widget_demo/homepage/home_page.dart';
import 'package:rx_widget_demo/homepage/homepage_model.dart';
import 'package:rx_widget_demo/keys.dart';
import 'package:rx_widget_demo/model_provider.dart';
import 'package:rx_widget_demo/service/weather_entry.dart';
-import 'package:collection/collection.dart';
-
import 'package:test/test.dart' as dart_test;
+import 'homepage_test.mocks.dart';
-
-class MockModel extends Mock implements HomePageModel {}
+// class MockModel extends Mock implements HomePageModel {}
//class MockCommand extends Mock implements RxCommand {}
-
-class MockStream extends Mock implements Stream{}
-
+// class MockStream extends Mock implements Stream {}
+@GenerateMocks([HomePageModel])
main() {
group('HomePage', () {
- testWidgets('Shows a loading spinner and disables the button while executing and shows the ListView on data arrival', (tester) async {
- final model = MockModel();
- final command = MockCommand>();
- final widget = ModelProvider(
+ testWidgets(
+ 'Shows a loading spinner and disables the button while '
+ 'executing and shows the ListView on data arrival', (tester) async {
+ final model = MockHomePageModel();
+ final command = MockCommand>();
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
+
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(
+ home: HomePage(),
+ ),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>command);
+ await tester.pumpWidget(widget); // Build initial State
+ await tester.pump();
-// model.updateWeatherCommand.canExecute.listen((b) => print("Can exceute: $b"));
-// model.updateWeatherCommand.isExecuting.listen((b) => print("Is Exceuting: $b"));
+ final textFinder = find.text('WeatherDemo');
+ expect(textFinder, findsOneWidget);
- await tester.pumpWidget(widget);// Build initial State
- await tester.pump();
+ final keyFinder = find.byKey(AppKeys.textField);
+ expect(keyFinder, findsOneWidget);
expect(find.byKey(AppKeys.loadingSpinner), findsNothing);
expect(find.byKey(AppKeys.updateButtonDisabled), findsNothing);
expect(find.byKey(AppKeys.updateButtonEnabled), findsOneWidget);
+ expect(find.byKey(AppKeys.widgetSelector), findsOneWidget);
expect(find.byKey(AppKeys.weatherList), findsNothing);
expect(find.byKey(AppKeys.loaderError), findsNothing);
expect(find.byKey(AppKeys.loaderPlaceHolder), findsOneWidget);
-
command.startExecution();
- await tester.pump();
- await tester.pump(); //because there are two streams involded it seems we have to pump twice so that both streambuilders can work
+ await tester.pump();
+ //because there are two streams involved it seems we have to pump twice so that both stream builders can work
+ await tester.pump();
expect(find.byKey(AppKeys.loadingSpinner), findsOneWidget);
expect(find.byKey(AppKeys.updateButtonDisabled), findsOneWidget);
@@ -59,7 +66,8 @@ main() {
expect(find.byKey(AppKeys.loaderError), findsNothing);
expect(find.byKey(AppKeys.loaderPlaceHolder), findsNothing);
- command.endExecutionWithData([ WeatherEntry("London", 10.0, 30.0, "sunny", 12)]);
+ command.endExecutionWithData(
+ [WeatherEntry("London", 10.0, 30.0, "sunny", 12)]);
await tester.pump(); // Build after Stream delivers value
expect(find.byKey(AppKeys.loadingSpinner), findsNothing);
@@ -70,22 +78,21 @@ main() {
expect(find.byKey(AppKeys.loaderPlaceHolder), findsNothing);
});
-
- testWidgets('shows place holder due to no data', (tester) async {
- final model = MockModel();
- final command = MockCommand>();
- final widget = ModelProvider(
+ testWidgets('shows placeHolder due to no data', (tester) async {
+ final model = MockHomePageModel();
+ final command = MockCommand>();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>command);
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
- // model.updateWeatherCommand.canExecute.listen((b) => print("Can exceute: $b"));
- // model.updateWeatherCommand.isExecuting.listen((b) => print("Is Exceuting: $b"));
+ // model.updateWeatherCommand.canExecute.listen((b) => print("Can exceute: $b"));
+ // model.updateWeatherCommand.isExecuting.listen((b) => print("Is Exceuting: $b"));
- await tester.pumpWidget(widget);// Build initial State
- await tester.pump();
+ await tester.pumpWidget(widget); // Build initial State
+ await tester.pump();
expect(find.byKey(AppKeys.loadingSpinner), findsNothing);
expect(find.byKey(AppKeys.updateButtonDisabled), findsNothing);
@@ -94,10 +101,10 @@ main() {
expect(find.byKey(AppKeys.loaderError), findsNothing);
expect(find.byKey(AppKeys.loaderPlaceHolder), findsOneWidget);
-
command.startExecution();
- await tester.pump();
- await tester.pump(); //because there are two streams involded it seems we have to pump twice so that both streambuilders can work
+ await tester.pump();
+ //because there are two streams involved it seems we have to pump twice so that both stream builders can work
+ await tester.pump();
expect(find.byKey(AppKeys.loadingSpinner), findsOneWidget);
expect(find.byKey(AppKeys.updateButtonDisabled), findsOneWidget);
@@ -106,7 +113,7 @@ main() {
expect(find.byKey(AppKeys.loaderError), findsNothing);
expect(find.byKey(AppKeys.loaderPlaceHolder), findsNothing);
- command.endExecutionWithData(null);
+ command.endExecutionWithData([]);
await tester.pump(); // Build after Stream delivers value
expect(find.byKey(AppKeys.loadingSpinner), findsNothing);
@@ -114,24 +121,24 @@ main() {
expect(find.byKey(AppKeys.updateButtonEnabled), findsOneWidget);
expect(find.byKey(AppKeys.weatherList), findsNothing);
expect(find.byKey(AppKeys.loaderError), findsNothing);
- expect(find.byKey(AppKeys.loaderPlaceHolder), findsOneWidget);
+ expect(find.text(noResultsText), findsOneWidget);
});
testWidgets('Shows error view due to received error', (tester) async {
- final model = MockModel();
- final command = MockCommand>();
- final widget = ModelProvider(
+ final model = MockHomePageModel();
+ final command = MockCommand>();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>command);
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
- // model.updateWeatherCommand.canExecute.listen((b) => print("Can exceute: $b"));
- // model.updateWeatherCommand.isExecuting.listen((b) => print("Is Exceuting: $b"));
+ // model.updateWeatherCommand.canExecute.listen((b) => print("Can exceute: $b"));
+ // model.updateWeatherCommand.isExecuting.listen((b) => print("Is Exceuting: $b"));
- await tester.pumpWidget(widget);// Build initial State
- await tester.pump();
+ await tester.pumpWidget(widget); // Build initial State
+ await tester.pump();
expect(find.byKey(AppKeys.loadingSpinner), findsNothing);
expect(find.byKey(AppKeys.updateButtonDisabled), findsNothing);
@@ -140,10 +147,10 @@ main() {
expect(find.byKey(AppKeys.loaderError), findsNothing);
expect(find.byKey(AppKeys.loaderPlaceHolder), findsOneWidget);
-
command.startExecution();
- await tester.pump();
- await tester.pump(); //because there are two streams involded it seems we have to pump twice so that both streambuilders can work
+ await tester.pump();
+ await tester
+ .pump(); //because there are two streams involded it seems we have to pump twice so that both streambuilders can work
expect(find.byKey(AppKeys.loadingSpinner), findsOneWidget);
expect(find.byKey(AppKeys.updateButtonDisabled), findsOneWidget);
@@ -163,104 +170,109 @@ main() {
expect(find.byKey(AppKeys.loaderPlaceHolder), findsNothing);
});
-
-
-
testWidgets('Tapping update button updates the weather', (tester) async {
- final model = MockModel();
- final command = MockCommand>();
- final widget = ModelProvider(
+ final model = MockHomePageModel();
+ final command = MockCommand>();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>command);
- when(model.updateWeatherCommand).thenAnswer((_)=>command);
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
- command.queueResultsForNextExecuteCall([CommandResult>(
- [WeatherEntry("London", 10.0, 30.0, "sunny", 12)],null, false)]);
+ command.queueResultsForNextExecuteCall([
+ CommandResult.data(
+ null, [WeatherEntry("London", 10.0, 30.0, "sunny", 12)]),
+ ]);
- expect(command.results, dart_test.emitsInOrder([ crm([WeatherEntry("London", 10.0, 30.0, "sunny", 12)], false, false) ]));
+ expect(
+ command.results,
+ dart_test.emitsInOrder([
+ crm([WeatherEntry("London", 10.0, 30.0, "sunny", 12)], false, false)
+ ]));
- command.results.listen((data)=> print("Received: " + data.data.toString()));
+ command.results
+ .listen((data) => print("Received: " + data.data.toString()));
await tester.pumpWidget(widget); // Build initial State
await tester.pump(); // Build after Stream delivers value
await tester.tap(find.byKey(AppKeys.updateButtonEnabled));
-
-
});
- testWidgets('calls updateWeatherCommand after text was entered in the textfield', (tester) async {
- final model = MockModel();
- final commandUpdate = MockCommand>();
- final commandTextChange = MockCommand();
- final widget = ModelProvider(
+ testWidgets(
+ 'calls updateWeatherCommand after text was entered in the textfield',
+ (tester) async {
+ final model = MockHomePageModel();
+ final commandUpdate = MockCommand>();
+ final commandTextChange = MockCommand();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>commandUpdate); //Allways needed because RxLoader binds to it
- when(model.textChangedCommand).thenAnswer((_)=>commandTextChange);
-
+ when(model.updateWeatherCommand).thenAnswer(
+ (_) => commandUpdate); //Allways needed because RxLoader binds to it
+ when(model.textChangedCommand).thenAnswer((_) => commandTextChange);
+
await tester.pumpWidget(widget); // Build initial State
await tester.enterText(find.byKey(AppKeys.textField), 'London');
await tester.pump(); // Build after text entered
await tester.tap(find.byKey(AppKeys.updateButtonEnabled));
- expect(commandTextChange.lastPassedValueToExecute,"London");
+ expect(commandTextChange.lastPassedValueToExecute, "London");
});
+ testWidgets('cannot tap update when commandUpdate is disabled',
+ (tester) async {
+ final model = MockHomePageModel();
+ final commandUpdate = MockCommand>(
+ restriction: Stream.value(false));
-
- testWidgets('cannot tap update when commandUpdate is disabled', (tester) async {
- final model = MockModel();
- final commandUpdate = MockCommand>(canExecute: Stream.value(false));
-
- final widget = ModelProvider(
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_)=>commandUpdate); //Allways needed because RxLoader binds to it
- when(model.updateWeatherCommand).thenAnswer((_)=>commandUpdate);
-
+ when(model.updateWeatherCommand).thenAnswer(
+ (_) => commandUpdate); //Allways needed because RxLoader binds to it
+ when(model.updateWeatherCommand).thenAnswer((_) => commandUpdate);
await tester.pumpWidget(widget); // Build initial State
await tester.pump(); // Build after Stream delivers value
await tester.pump(); // Build after Stream delivers value
- expect(find.byKey(AppKeys.updateButtonDisabled), findsOneWidget); // should display disabled button
- expect(find.byKey(AppKeys.updateButtonEnabled), findsNothing); // should not display enabled button
-
+ expect(find.byKey(AppKeys.updateButtonDisabled),
+ findsOneWidget); // should display disabled button
+ expect(find.byKey(AppKeys.updateButtonEnabled),
+ findsNothing); // should not display enabled button
await tester.tap(find.byKey(AppKeys.updateButtonDisabled));
expect(commandUpdate.executionCount, 0);
});
-
-
testWidgets('tapping switch toggles model', (tester) async {
- final model = MockModel();
- final updateCommand = MockCommand>(canExecute: Stream.value(false));
- final switchCommand = MockCommand();
- final widget = ModelProvider(
+ final model = MockHomePageModel();
+ final updateCommand = MockCommand>(
+ restriction: Stream.value(false));
+ final switchCommand = MockCommand();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
when(model.updateWeatherCommand).thenAnswer((_) => updateCommand);
when(model.switchChangedCommand).thenAnswer((_) => switchCommand);
await tester.pumpWidget(widget); // Build initial State
- await tester.pump();
+ await tester.pump();
await tester.tap(find.byKey(AppKeys.updateSwitch));
// Starts out true, tapping should go false
expect(switchCommand.lastPassedValueToExecute, false);
- await tester.pump();
+ await tester.pump();
// tap again
await tester.tap(find.byKey(AppKeys.updateSwitch));
@@ -268,60 +280,55 @@ main() {
expect(switchCommand.lastPassedValueToExecute, true);
});
-
- testWidgets('Tapping update button clears the filter field', (tester) async {
- final model = MockModel();
- final command = MockCommand>();
- final widget = ModelProvider(
+ testWidgets('Tapping update button clears the filter field',
+ (tester) async {
+ final model = MockHomePageModel();
+ final command = MockCommand>();
+ final widget = ModelProvider(
model: model,
- child: MaterialApp(home: HomePage()),
+ child: MaterialApp(home: HomePage()),
);
- when(model.updateWeatherCommand).thenAnswer((_) =>command);
+ when(model.updateWeatherCommand).thenAnswer((_) => command);
+ when(model.textChangedCommand).thenAnswer(
+ (Invocation realInvocation) => RxCommand.createSync((s) => s));
+ const keyword = 'Tripoli';
await tester.pumpWidget(widget); // Build initial State
- await tester.enterText(find.byKey(AppKeys.textField), 'London');
+ await tester.pump();
+ await tester.enterText(find.byKey(AppKeys.textField), keyword);
+
+ final textField = tester.widget(find.byKey(AppKeys.textField));
+ final controller = textField.controller!;
+
+ expect(controller.text, keyword);
await tester.pump(); // Build after Stream delivers value
await tester.tap(find.byKey(AppKeys.updateButtonEnabled));
-
- expect(tester.widget(find.byKey(AppKeys.textField)).controller.text.length, 0);
+ expect(controller.text.length, 0);
});
-
-
-
});
}
+dart_test.StreamMatcher crm(
+ List data, bool hasError, bool isExecuting) {
+ return dart_test.StreamMatcher((x) async {
+ final event = await x.next as CommandResult>;
+ if (event.data != null) {
+ if (!ListEquality().equals(event.data, data)) {
+ return "Data not equal";
+ }
+ }
+ if (!hasError && event.error != null) return "Had error while not expected";
+ if (hasError && !(event.error is Exception)) return "Wong error type";
+ if (event.isExecuting != isExecuting)
+ return "Wong isExecuting $isExecuting";
-
-
- dart_test.StreamMatcher crm(List data, bool hasError, bool isExceuting)
- {
- return dart_test.StreamMatcher((x) async {
- var event = await x.next as CommandResult>;
- if (event.data != null)
- {
- if (!ListEquality().equals(event.data, data))
- {
- return "Data not equal";
- }
- }
-
- if (!hasError && event.error != null)
- return "Had error while not expected";
-
- if (hasError && !(event.error is Exception))
- return "Wong error type";
-
- if (event.isExecuting != isExceuting)
- return "Wong isExecuting $isExceuting";
-
- return null;
- }, "Wrong value emmited:");
- }
+ return null;
+ }, "Wrong value emitted:");
+}
diff --git a/example/test/homepage_test.mocks.dart b/example/test/homepage_test.mocks.dart
new file mode 100644
index 0000000..0424e49
--- /dev/null
+++ b/example/test/homepage_test.mocks.dart
@@ -0,0 +1,45 @@
+// Mocks generated by Mockito 5.0.5 from annotations
+// in rx_widget_demo/test/homepage_test.dart.
+// Do not manually edit this file.
+
+import 'package:mockito/mockito.dart' as _i1;
+import 'package:rx_command/rx_command.dart' as _i3;
+import 'package:rx_widget_demo/homepage/homepage_model.dart' as _i4;
+import 'package:rx_widget_demo/service/weather_entry.dart' as _i5;
+import 'package:rx_widget_demo/service/weather_service.dart' as _i2;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeWeatherService extends _i1.Fake implements _i2.WeatherService {}
+
+class _FakeRxCommand extends _i1.Fake
+ implements _i3.RxCommand {}
+
+/// A class which mocks [HomePageModel].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockHomePageModel extends _i1.Mock implements _i4.HomePageModel {
+ MockHomePageModel() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i2.WeatherService get service =>
+ (super.noSuchMethod(Invocation.getter(#service),
+ returnValue: _FakeWeatherService()) as _i2.WeatherService);
+ @override
+ _i3.RxCommand> get updateWeatherCommand =>
+ (super.noSuchMethod(Invocation.getter(#updateWeatherCommand),
+ returnValue: _FakeRxCommand>())
+ as _i3.RxCommand>);
+ @override
+ _i3.RxCommand get switchChangedCommand => (super.noSuchMethod(
+ Invocation.getter(#switchChangedCommand),
+ returnValue: _FakeRxCommand()) as _i3.RxCommand);
+ @override
+ _i3.RxCommand get textChangedCommand =>
+ (super.noSuchMethod(Invocation.getter(#textChangedCommand),
+ returnValue: _FakeRxCommand())
+ as _i3.RxCommand);
+}
diff --git a/example/test/weather_service_test.dart b/example/test/weather_service_test.dart
index fef897c..769dfaa 100644
--- a/example/test/weather_service_test.dart
+++ b/example/test/weather_service_test.dart
@@ -1,40 +1,40 @@
-
import 'package:http/http.dart' as http;
+import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:rx_widget_demo/service/weather_service.dart';
import 'package:test/test.dart';
-class MockClient extends Mock implements http.Client {}
+import 'weather_service_test.mocks.dart';
+
+// class MockClient extends Mock implements http.Client {}
-main() {
+@GenerateMocks([http.Client])
+void main() {
group('WeatherService', () {
test('should return all items when empty filter provided', () async {
- final client = new MockClient();
- final service = new WeatherService(client);
- when(client.get(WeatherService.url))
- .thenAnswer((_) async => new http.Response(responseBody, 200));
-
+ final client = MockClient();
+ final service = WeatherService(client);
+ when(client.get(WeatherService.uri))
+ .thenAnswer((_) async => http.Response(responseBody, 200));
final entries = await service.getWeatherEntriesForCity('');
-
expect(entries.length, 3);
});
test('should return all items when null filter provided', () async {
- final client = new MockClient();
- final service = new WeatherService(client);
- when(client.get(WeatherService.url))
- .thenAnswer((_) async => new http.Response(responseBody, 200));
-
+ final client = MockClient();
+ final service = WeatherService(client);
+ when(client.get(WeatherService.uri))
+ .thenAnswer((_) async => http.Response(responseBody, 200));
final entries = await service.getWeatherEntriesForCity(null);
expect(entries.length, 3);
});
test('should filter the entries when given a city name', () async {
- final client = new MockClient();
- final service = new WeatherService(client);
- when(client.get(WeatherService.url))
- .thenAnswer((_) async => new http.Response(responseBody, 200));
+ final client = MockClient();
+ final service = WeatherService(client);
+ when(client.get(WeatherService.uri))
+ .thenAnswer((_) async => http.Response(responseBody, 200));
final entries = await service.getWeatherEntriesForCity('Dole');
@@ -42,19 +42,19 @@ main() {
});
test('should throw an exception when the response is not 200', () async {
- final client = new MockClient();
- final service = new WeatherService(client);
- when(client.get(WeatherService.url))
- .thenAnswer((_) async => new http.Response('Error', 404));
+ final client = MockClient();
+ final service = WeatherService(client);
+ when(client.get(WeatherService.uri))
+ .thenAnswer((_) async => http.Response('Error', 404));
expect(service.getWeatherEntriesForCity('Dole'), throwsException);
});
test('should throw an exception when the json is malformed', () async {
- final client = new MockClient();
- final service = new WeatherService(client);
- when(client.get(WeatherService.url))
- .thenAnswer((_) async => new http.Response('p[2p[1p[ppadsdaf', 200));
+ final client = MockClient();
+ final service = WeatherService(client);
+ when(client.get(WeatherService.uri))
+ .thenAnswer((_) async => http.Response('p[2p[1p[ppadsdaf', 200));
expect(service.getWeatherEntriesForCity('Dole'), throwsException);
});
diff --git a/example/test/weather_service_test.mocks.dart b/example/test/weather_service_test.mocks.dart
new file mode 100644
index 0000000..97966b6
--- /dev/null
+++ b/example/test/weather_service_test.mocks.dart
@@ -0,0 +1,102 @@
+// Mocks generated by Mockito 5.0.5 from annotations
+// in rx_widget_demo/test/weather_service_test.dart.
+// Do not manually edit this file.
+
+import 'dart:async' as _i6;
+import 'dart:convert' as _i7;
+import 'dart:typed_data' as _i3;
+
+import 'package:http/src/base_request.dart' as _i8;
+import 'package:http/src/client.dart' as _i5;
+import 'package:http/src/response.dart' as _i2;
+import 'package:http/src/streamed_response.dart' as _i4;
+import 'package:mockito/mockito.dart' as _i1;
+
+// ignore_for_file: comment_references
+// ignore_for_file: unnecessary_parenthesis
+
+class _FakeResponse extends _i1.Fake implements _i2.Response {}
+
+class _FakeUint8List extends _i1.Fake implements _i3.Uint8List {}
+
+class _FakeStreamedResponse extends _i1.Fake implements _i4.StreamedResponse {}
+
+/// A class which mocks [Client].
+///
+/// See the documentation for Mockito's code generation for more information.
+class MockClient extends _i1.Mock implements _i5.Client {
+ MockClient() {
+ _i1.throwOnMissingStub(this);
+ }
+
+ @override
+ _i6.Future<_i2.Response> head(Uri? url, {Map? headers}) =>
+ (super.noSuchMethod(Invocation.method(#head, [url], {#headers: headers}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future<_i2.Response> get(Uri? url, {Map? headers}) =>
+ (super.noSuchMethod(Invocation.method(#get, [url], {#headers: headers}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future<_i2.Response> post(Uri? url,
+ {Map? headers,
+ Object? body,
+ _i7.Encoding? encoding}) =>
+ (super.noSuchMethod(
+ Invocation.method(#post, [url],
+ {#headers: headers, #body: body, #encoding: encoding}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future<_i2.Response> put(Uri? url,
+ {Map? headers,
+ Object? body,
+ _i7.Encoding? encoding}) =>
+ (super.noSuchMethod(
+ Invocation.method(#put, [url],
+ {#headers: headers, #body: body, #encoding: encoding}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future<_i2.Response> patch(Uri? url,
+ {Map? headers,
+ Object? body,
+ _i7.Encoding? encoding}) =>
+ (super.noSuchMethod(
+ Invocation.method(#patch, [url],
+ {#headers: headers, #body: body, #encoding: encoding}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future<_i2.Response> delete(Uri? url,
+ {Map? headers,
+ Object? body,
+ _i7.Encoding? encoding}) =>
+ (super.noSuchMethod(
+ Invocation.method(#delete, [url],
+ {#headers: headers, #body: body, #encoding: encoding}),
+ returnValue: Future<_i2.Response>.value(_FakeResponse()))
+ as _i6.Future<_i2.Response>);
+ @override
+ _i6.Future read(Uri? url, {Map? headers}) =>
+ (super.noSuchMethod(Invocation.method(#read, [url], {#headers: headers}),
+ returnValue: Future.value('')) as _i6.Future);
+ @override
+ _i6.Future<_i3.Uint8List> readBytes(Uri? url,
+ {Map? headers}) =>
+ (super.noSuchMethod(
+ Invocation.method(#readBytes, [url], {#headers: headers}),
+ returnValue: Future<_i3.Uint8List>.value(_FakeUint8List()))
+ as _i6.Future<_i3.Uint8List>);
+ @override
+ _i6.Future<_i4.StreamedResponse> send(_i8.BaseRequest? request) =>
+ (super.noSuchMethod(Invocation.method(#send, [request]),
+ returnValue:
+ Future<_i4.StreamedResponse>.value(_FakeStreamedResponse()))
+ as _i6.Future<_i4.StreamedResponse>);
+ @override
+ void close() => super.noSuchMethod(Invocation.method(#close, []),
+ returnValueForMissingStub: null);
+}
diff --git a/lib/src/reactive_base_widget.dart b/lib/src/reactive_base_widget.dart
index 137d2bb..1f60628 100644
--- a/lib/src/reactive_base_widget.dart
+++ b/lib/src/reactive_base_widget.dart
@@ -1,21 +1,21 @@
import 'dart:io';
+
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
abstract class ReactiveBaseWidget extends StatefulWidget {
final Stream stream;
- final T initialData;
+ final T? initialData;
@mustCallSuper
- const ReactiveBaseWidget(this.stream, this.initialData, {Key key})
- : assert(stream != null),
- super(key: key);
+ const ReactiveBaseWidget(this.stream, this.initialData, {Key? key}) : super(key: key);
Widget build(BuildContext context, T data);
+
Widget errorBuild(BuildContext context, Object error) {
return Center(
- child: Text(error),
+ child: Text(error.toString()),
);
}
@@ -35,9 +35,8 @@ class _ReactiveBaseWidgetState extends State> {
initialData: widget.initialData,
stream: widget.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
- if (snapshot.hasError)
- return widget.errorBuild(context, snapshot.error);
- if (snapshot.hasData) return widget.build(context, snapshot.data);
+ if (snapshot.hasError) return widget.errorBuild(context, snapshot.error!);
+ if (snapshot.hasData) return widget.build(context, snapshot.data!);
return widget.placeHolderBuild(context);
},
);
diff --git a/lib/src/reactive_builder.dart b/lib/src/reactive_builder.dart
index a331a4f..3a45460 100644
--- a/lib/src/reactive_builder.dart
+++ b/lib/src/reactive_builder.dart
@@ -5,14 +5,14 @@ import 'reactive_base_widget.dart';
class ReactiveBuilder extends ReactiveBaseWidget {
final RxBuilder builder;
- final ErrorBuilder errorBuilder;
- final PlaceHolderBuilder placeHolderBuilder;
+ final ErrorBuilder? errorBuilder;
+ final PlaceHolderBuilder? placeHolderBuilder;
const ReactiveBuilder({
- Key key,
- @required Stream stream,
- T initialData,
- @required this.builder,
+ Key? key,
+ required Stream stream,
+ T? initialData,
+ required this.builder,
this.placeHolderBuilder,
this.errorBuilder,
}) : super(stream, initialData, key: key);
@@ -22,13 +22,13 @@ class ReactiveBuilder extends ReactiveBaseWidget {
@override
Widget placeHolderBuild(BuildContext context) {
- if (placeHolderBuilder != null) return placeHolderBuilder(context);
+ if (placeHolderBuilder != null) return placeHolderBuilder!(context);
return super.placeHolderBuild(context);
}
@override
Widget errorBuild(BuildContext context, Object error) {
- if (errorBuilder != null) return errorBuilder(context, error);
+ if (errorBuilder != null) return errorBuilder!(context, error);
return super.errorBuild(context, error);
}
}
diff --git a/lib/src/reactive_widget.dart b/lib/src/reactive_widget.dart
index 151877b..d86d18b 100644
--- a/lib/src/reactive_widget.dart
+++ b/lib/src/reactive_widget.dart
@@ -5,32 +5,30 @@ import 'reactive_base_widget.dart';
class ReactiveWidget extends ReactiveBaseWidget {
final RxWidget widget;
- final RxErrorWidget errorWidget;
- final Widget placeHolderWidget;
+ final RxErrorWidget? errorWidget;
+ final Widget? placeHolderWidget;
const ReactiveWidget({
- Key key,
- @required Stream stream,
- T initialData,
- @required this.widget,
+ Key? key,
+ required Stream stream,
+ T? initialData,
+ required this.widget,
this.placeHolderWidget,
this.errorWidget,
- }) : assert(stream != null),
- assert(widget != null),
- super(stream, initialData, key: key);
+ }) : super(stream, initialData, key: key);
@override
Widget build(BuildContext context, T data) => widget(data);
@override
Widget placeHolderBuild(BuildContext context) {
- if (placeHolderWidget != null) return placeHolderWidget;
+ if (placeHolderWidget != null) return placeHolderWidget!;
return super.placeHolderBuild(context);
}
@override
Widget errorBuild(BuildContext context, Object error) {
- if (errorWidget != null) return errorWidget(error);
+ if (errorWidget != null) return errorWidget!(error);
return super.errorBuild(context, error);
}
}
diff --git a/lib/src/rx_command_builder.dart b/lib/src/rx_command_builder.dart
index e7887a2..bc52b11 100644
--- a/lib/src/rx_command_builder.dart
+++ b/lib/src/rx_command_builder.dart
@@ -4,20 +4,20 @@ import 'package:flutter/material.dart';
import 'package:rx_command/rx_command.dart';
import 'package:rx_widgets/src/builder_functions.dart';
-/// Spinner/Busy indicator that reacts on the output of a `Stream>`.
+/// Spinner/Busy indicator that reacts on the output of a `Stream>`.
/// It's made especially to work together with `RxCommand` from the `rx_command`package.
/// it starts running as soon as an item with `isExecuting==true` is received until `isExecuting==true` is received.
/// To react on other possible states (`data, no data, error`) that can be emitted it offers three option `Builder` methods.
-class RxCommandBuilder extends StatelessWidget {
- final Stream> commandResults;
- final RxBuilder dataBuilder;
- final ErrorBuilder errorBuilder;
- final BusyBuilder busyBuilder;
- final PlaceHolderBuilder placeHolderBuilder;
- final TargetPlatform platform;
+class RxCommandBuilder extends StatelessWidget {
+ final Stream> commandResults;
+ final RxBuilder? dataBuilder;
+ final ErrorBuilder>? errorBuilder;
+ final BusyBuilder? busyBuilder;
+ final PlaceHolderBuilder? placeHolderBuilder;
+ final TargetPlatform? platform;
/// Creates a new `RxCommandBuilder` instance
- /// [commandResults] : `Stream>` or a `RxCommand` that issues `CommandResults`
+ /// [commandResults] : `Stream>` or a `RxCommand` that issues `CommandResults`
/// [busyBuilder] : Builder that will be called as soon as an event with `isExecuting==true`.
/// [dataBuilder] : Builder that will be called as soon as an event with data is received. It will get passed the `data` field of the CommandResult.
/// If this is null a `Container` will be created instead.
@@ -26,42 +26,39 @@ class RxCommandBuilder extends StatelessWidget {
/// [dataBuilder] : Builder that will be called as soon as an event with an `error` is received. It will get passed the `error` field of the CommandResult.
/// If this is null a `Container` will be created instead.
const RxCommandBuilder({
- Key key,
- @required this.commandResults,
+ Key? key,
+ required this.commandResults,
this.platform,
this.busyBuilder,
this.dataBuilder,
this.placeHolderBuilder,
this.errorBuilder,
- }) : assert(commandResults != null),
- super(key: key);
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
- return StreamBuilder>(
+ return StreamBuilder>(
stream: commandResults,
builder: (context, snapshot) {
- CommandResult item;
+ CommandResult? item;
if (snapshot.hasData) {
item = snapshot.data;
} else if (snapshot.hasError) {
- item = CommandResult.error(snapshot.error);
+ item = CommandResult.error(null, snapshot.error);
} else {
- item = const CommandResult(null, null, false);
+ item = const CommandResult.blank();
}
- return _processItem(context, item);
+ return _processItem(context, item!);
},
);
}
- Widget _processItem(BuildContext context, CommandResult item) {
- assert(item != null);
-
+ Widget _processItem(BuildContext context, CommandResult item) {
if (item.isExecuting) {
if (busyBuilder != null) {
- return busyBuilder(context);
+ return busyBuilder!(context);
} else {
- final spinner = (platform ?? defaultTargetPlatform == TargetPlatform.iOS)
+ final spinner = ((platform ?? defaultTargetPlatform) == TargetPlatform.iOS)
? const CupertinoActivityIndicator()
: const CircularProgressIndicator();
return Center(child: spinner);
@@ -70,7 +67,7 @@ class RxCommandBuilder extends StatelessWidget {
if (item.hasData) {
if (dataBuilder != null) {
- return dataBuilder(context, item.data);
+ return dataBuilder!(context, item.data!);
} else {
return const SizedBox();
}
@@ -78,14 +75,15 @@ class RxCommandBuilder extends StatelessWidget {
if (item.hasError) {
if (errorBuilder != null) {
- return errorBuilder(context, item.error);
+ final commandError = item.error is CommandError ? item.error! : CommandError(item.paramData, item.error);
+ return errorBuilder!(context, commandError);
} else {
return const SizedBox();
}
}
if (placeHolderBuilder != null) {
- return placeHolderBuilder(context);
+ return placeHolderBuilder!(context);
} else {
return const SizedBox();
}
diff --git a/lib/src/rx_command_handler_mixin.dart b/lib/src/rx_command_handler_mixin.dart
index 793630d..018555f 100644
--- a/lib/src/rx_command_handler_mixin.dart
+++ b/lib/src/rx_command_handler_mixin.dart
@@ -11,7 +11,8 @@ mixin RxCommandHandlerMixin on StatelessWidget {
final _state = _MixinState();
@override
- StatelessElement createElement() => _StatelessMixInElement(this);
+ StatelessElement createElement() =>
+ _StatelessMixInElement(this);
RxCommandListener get commandListener;
}
@@ -25,19 +26,21 @@ mixin RxCommandStatefulHandlerMixin on StatefulWidget {
final _state = _MixinState();
@override
- StatefulElement createElement() => _StatefulMixInElement(this);
+ StatefulElement createElement() =>
+ _StatefulMixInElement(this);
RxCommandListener get commandListener;
}
-class _StatelessMixInElement extends StatelessElement {
+class _StatelessMixInElement
+ extends StatelessElement {
_StatelessMixInElement(W widget) : super(widget);
@override
- W get widget => super.widget;
+ W get widget => super.widget as W;
@override
- void mount(Element parent, newSlot) {
+ void mount(Element? parent, newSlot) {
widget._state.init(widget.commandListener);
super.mount(parent, newSlot);
}
@@ -49,15 +52,15 @@ class _StatelessMixInElement extends Stat
}
}
-
-class _StatefulMixInElement extends StatefulElement {
+class _StatefulMixInElement
+ extends StatefulElement {
_StatefulMixInElement(W widget) : super(widget);
@override
- W get widget => super.widget;
+ W get widget => super.widget as W;
@override
- void mount(Element parent, newSlot) {
+ void mount(Element? parent, newSlot) {
widget._state.init(widget.commandListener);
super.mount(parent, newSlot);
}
@@ -69,11 +72,10 @@ class _StatefulMixInElement exten
}
}
-
class _MixinState {
- RxCommandListener _listener;
+ RxCommandListener? _listener;
void init(RxCommandListener listener) => _listener = listener;
void dispose() => _listener?.dispose();
-}
\ No newline at end of file
+}
diff --git a/lib/src/rx_raised_button.dart b/lib/src/rx_raised_button.dart
index a11e2ed..896975d 100644
--- a/lib/src/rx_raised_button.dart
+++ b/lib/src/rx_raised_button.dart
@@ -5,28 +5,29 @@ import 'package:rx_command/rx_command.dart';
/// so the button gets disabled if the `rxCommand` has the `canExecute` set to `false` or when it is executing
class RxRaisedButton extends StatelessWidget {
final RxCommand rxCommand;
- final ValueChanged onHighlightChanged;
- final ButtonTextTheme textTheme;
- final Color textColor;
- final Color disabledTextColor;
- final Color color;
- final Color disabledColor;
- final Color highlightColor;
- final Color splashColor;
- final Brightness colorBrightness;
- final double elevation;
- final double highlightElevation;
- final double disabledElevation;
- final EdgeInsetsGeometry padding;
- final ShapeBorder shape;
+ final ValueChanged? onHighlightChanged;
+ final ButtonTextTheme? textTheme;
+ final Color? textColor;
+ final Color? disabledTextColor;
+ final Color? color;
+ final Color? disabledColor;
+ final Color? highlightColor;
+ final Color? splashColor;
+ final Brightness? colorBrightness;
+ final double? elevation;
+ final double? highlightElevation;
+ final double? disabledElevation;
+ final EdgeInsetsGeometry? padding;
+ final ShapeBorder? shape;
final Clip clipBehavior = Clip.none;
- final MaterialTapTargetSize materialTapTargetSize;
- final Duration animationDuration;
- final Widget child;
+ final MaterialTapTargetSize? materialTapTargetSize;
+ final Duration? animationDuration;
+ final Widget? child;
+
RxRaisedButton({
- Key key,
+ Key? key,
this.child,
- this.rxCommand,
+ required this.rxCommand,
this.onHighlightChanged,
this.textTheme,
this.textColor,
@@ -44,13 +45,15 @@ class RxRaisedButton extends StatelessWidget {
this.materialTapTargetSize,
this.animationDuration,
}) : super(key: key);
+
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: rxCommand.canExecute,
builder: (context, snapshot) {
+ // ignore: deprecated_member_use
return RaisedButton(
- onPressed: snapshot.data ? rxCommand : null,
+ onPressed: snapshot.hasData ? () => rxCommand() : null,
onHighlightChanged: onHighlightChanged,
textTheme: textTheme,
textColor: textColor,
diff --git a/lib/src/rx_spinner.dart b/lib/src/rx_spinner.dart
index 3947106..acb08ae 100644
--- a/lib/src/rx_spinner.dart
+++ b/lib/src/rx_spinner.dart
@@ -1,11 +1,12 @@
import 'dart:async';
+
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:rx_command/rx_command.dart';
import 'package:rx_widgets/src/builder_functions.dart';
import 'package:rx_widgets/src/widget_selector.dart';
-import 'package:flutter/material.dart';
/// Spinner/Busy Indicator that reacts on the output of a `Stream` it starts running as soon as a `true` value is received
/// until the next `false`is emitted. If the Spinner should replace another Widget while Spinning this widget can be passed as `normal` parameter.
@@ -13,16 +14,16 @@ import 'package:flutter/material.dart';
/// Needless to say that `RxSpinner` is ideal in combination with `RxCommand's` `isExecuting` Observable
class RxSpinner extends StatelessWidget {
final Stream busyEvents;
- final Widget normal;
+ final Widget? normal;
- final TargetPlatform platform;
+ final TargetPlatform? platform;
final double radius;
- final Color backgroundColor;
- final Animation valueColor;
+ final Color? backgroundColor;
+ final Animation? valueColor;
final double strokeWidth;
- final double value;
+ final double? value;
/// Creates a new RxSpinner instance
/// `busyEvents` : `Stream` that controls the activity of the Spinner. On receiving `true` it replaces the `normal` widget
@@ -33,7 +34,7 @@ class RxSpinner extends StatelessWidget {
/// all other parameters please see https://docs.flutter.io/flutter/material/CircularProgressIndicator-class.html
/// they are ignored if the platform style is iOS.
const RxSpinner(
- {this.busyEvents,
+ {required this.busyEvents,
this.platform,
this.radius = 20.0,
this.backgroundColor,
@@ -41,9 +42,8 @@ class RxSpinner extends StatelessWidget {
this.valueColor,
this.strokeWidth: 4.0,
this.normal,
- Key key})
- : assert(busyEvents != null),
- super(key: key);
+ Key? key})
+ : super(key: key);
@override
Widget build(BuildContext context) {
@@ -62,42 +62,35 @@ class RxSpinner extends StatelessWidget {
return WidgetSelector(
buildEvents: busyEvents,
- onTrue: Center(
- child: Container(
- width: this.radius * 2, height: this.radius * 2, child: spinner)),
+ onTrue: Center(child: Container(width: this.radius * 2, height: this.radius * 2, child: spinner)),
onFalse: normal != null ? normal : Container(),
);
}
}
-/*
-typedef BuilderFunction = Widget Function(BuildContext context, T data);
-typedef BuilderFunction1 = Widget Function(BuildContext context);
-*/
-/// Spinner/Busyindicator that reacts on the output of a `Stream>`. It's made especially to work together with
-/// `RxCommand` from the `rx_command`package.
-/// it starts running as soon as an item with `isExecuting==true` is received
-/// until `isExecuting==true` is received.
-/// To react on other possible states (`data, nodata, error`) that can be emitted it offers three option `Builder` methods
-class RxLoader extends StatefulWidget {
- final Stream> commandResults;
- final RxBuilder dataBuilder;
- final ErrorBuilder errorBuilder;
- final PlaceHolderBuilder placeHolderBuilder;
-
- final TargetPlatform platform;
+/// Spinner/Busy indicator that reacts on the output of a `Stream>`.
+/// It's made especially to work together with `RxCommand` from the `rx_command`package.
+/// It starts running as soon as an item with `isExecuting==true` is received until `isExecuting==true` is received.
+/// To react on other possible states (`data, no data, error`) that can be emitted it offers three option `Builder` methods
+class RxLoader extends StatefulWidget {
+ final Stream> commandResults;
+ final RxBuilder? dataBuilder;
+ final ErrorBuilder? errorBuilder;
+ final PlaceHolderBuilder? placeHolderBuilder;
+
+ final TargetPlatform? platform;
final double radius;
- final Color backgroundColor;
- final Animation valueColor;
+ final Color? backgroundColor;
+ final Animation? valueColor;
final double strokeWidth;
- final double value;
+ final double? value;
- final Key spinnerKey;
+ final Key? spinnerKey;
/// Creates a new `RxLoader` instance
- /// [commandResults] : `Stream>` or a `RxCommand` that issues `CommandResults`
+ /// [commandResults] : `Stream>` or a `RxCommand` that issues `CommandResults`
/// [platform] : defines platform style of the Spinner. If this is null or not provided the style of the current platform will be used
/// [radius] : radius of the Spinner
/// [dataBuilder] : Builder that will be called as soon as an event with data is received. It will get passed the `data` feeld of the CommandResult.
@@ -110,9 +103,9 @@ class RxLoader extends StatefulWidget {
/// all other parameters please see https://docs.flutter.io/flutter/material/CircularProgressIndicator-class.html
/// they are ignored if the platform style is iOS.
const RxLoader({
- Key key,
+ Key? key,
this.spinnerKey,
- this.commandResults,
+ required this.commandResults,
this.platform,
this.radius = 20.0,
this.backgroundColor,
@@ -122,21 +115,20 @@ class RxLoader extends StatefulWidget {
this.dataBuilder,
this.placeHolderBuilder,
this.errorBuilder,
- }) : assert(commandResults != null),
- super(key: key);
+ }) : super(key: key);
@override
_RxLoaderState createState() {
- return _RxLoaderState(commandResults);
+ return _RxLoaderState(commandResults);
}
}
-class _RxLoaderState extends State