[#43] Handling bad configuration. Advanced connection error handling

This commit is contained in:
estevez 2018-09-20 23:21:03 +03:00
parent e5fbc40c66
commit eceece5a90
3 changed files with 95 additions and 40 deletions

View File

@ -6,6 +6,12 @@ class StateChangedEvent {
StateChangedEvent(this.entityId); StateChangedEvent(this.entityId);
} }
class SettingsChangedEvent {
bool reconnect;
SettingsChangedEvent(this.reconnect);
}
class HassioDataModel { class HassioDataModel {
String _hassioAPIEndpoint; String _hassioAPIEndpoint;
String _hassioPassword; String _hassioPassword;
@ -56,7 +62,7 @@ class HassioDataModel {
closeConnection() { closeConnection() {
if (_hassioChannel?.closeCode == null) { if (_hassioChannel?.closeCode == null) {
_hassioChannel.sink?.close(); _hassioChannel?.sink?.close();
} }
_hassioChannel = null; _hassioChannel = null;
} }
@ -111,7 +117,7 @@ class HassioDataModel {
_sendSubscribe(); _sendSubscribe();
connectionCompleter.complete(); connectionCompleter.complete();
} else if (data["type"] == "auth_invalid") { } else if (data["type"] == "auth_invalid") {
connectionCompleter.completeError({message: "Auth error: ${data["message"]}"}); connectionCompleter.completeError({"errorCode": 6, "errorMessage": "${data["message"]}"});
} else if (data["type"] == "result") { } else if (data["type"] == "result") {
if (data["id"] == _configMessageId) { if (data["id"] == _configMessageId) {
_parseConfig(data); _parseConfig(data);

View File

@ -51,8 +51,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
Map _instanceConfig; Map _instanceConfig;
int _uiViewsCount = 0; int _uiViewsCount = 0;
String _instanceHost; String _instanceHost;
int _fetchErrorCode = 0; int _errorCodeToBeShown = 0;
bool loading = true; String _lastErrorMessage = "";
StreamSubscription _stateSubscription;
bool _isLoading = true;
Map _stateIconColors = { Map _stateIconColors = {
"on": Colors.amber, "on": Colors.amber,
"off": Colors.blueGrey, "off": Colors.blueGrey,
@ -65,7 +67,14 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_init(); eventBus.on<SettingsChangedEvent>().listen((event) {
debugPrint("Settings change event: reconnect=${event.reconnect}");
setState(() {
_errorCodeToBeShown = 0;
});
_initConnection();
});
_initConnection();
} }
@override @override
@ -76,16 +85,29 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
} }
_init() async { _initConnection() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
String domain = prefs.getString('hassio-domain'); String domain = prefs.getString('hassio-domain');
String port = prefs.getString('hassio-port'); String port = prefs.getString('hassio-port');
_instanceHost = "$domain:$port"; _instanceHost = "$domain:$port";
String _hassioAPIEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket"; String apiEndpoint = "${prefs.getString('hassio-protocol')}://$domain:$port/api/websocket";
String _hassioPassword = prefs.getString('hassio-password'); String apiPassword = prefs.getString('hassio-password');
_dataModel = HassioDataModel(_hassioAPIEndpoint, _hassioPassword); if ((domain == null) || (port == null) || (apiEndpoint == null) || (apiPassword == null) ||
(domain.length == 0) || (port.length == 0) || (apiEndpoint.length == 0) || (apiPassword.length == 0)) {
setState(() {
_errorCodeToBeShown = 5;
});
} else {
if (_dataModel != null) _dataModel.closeConnection();
_createConnection(apiEndpoint, apiPassword);
}
}
_createConnection(String apiEndpoint, String apiPassword) {
_dataModel = HassioDataModel(apiEndpoint, apiPassword);
_refreshData(); _refreshData();
eventBus.on<StateChangedEvent>().listen((event) { if (_stateSubscription != null) _stateSubscription.cancel();
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
debugPrint("State change event for ${event.entityId}"); debugPrint("State change event for ${event.entityId}");
setState(() { setState(() {
_entitiesData = _dataModel.entities; _entitiesData = _dataModel.entities;
@ -95,9 +117,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_refreshData() async { _refreshData() async {
setState(() { setState(() {
loading = true; _isLoading = true;
}); });
_fetchErrorCode = 0; _errorCodeToBeShown = 0;
if (_dataModel != null) { if (_dataModel != null) {
await _dataModel.fetch().then((result) { await _dataModel.fetch().then((result) {
setState(() { setState(() {
@ -105,12 +127,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_entitiesData = _dataModel.entities; _entitiesData = _dataModel.entities;
_uiStructure = _dataModel.uiStructure; _uiStructure = _dataModel.uiStructure;
_uiViewsCount = _uiStructure.length; _uiViewsCount = _uiStructure.length;
loading = false; _isLoading = false;
}); });
}).catchError((e) { }).catchError((e) {
setState(() { setState(() {
_fetchErrorCode = e["errorCode"] != null ? e["errorCode"] : 2; _errorCodeToBeShown = e["errorCode"] != null ? e["errorCode"] : 99;
loading = false; _lastErrorMessage = e["errorMessage"] ?? "Unknown error";
_isLoading = false;
}); });
}); });
} }
@ -259,7 +282,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
Row titleRow = Row( Row titleRow = Row(
children: [Text(_instanceConfig != null ? _instanceConfig["location_name"] : "")], children: [Text(_instanceConfig != null ? _instanceConfig["location_name"] : "")],
); );
if (loading) { if (_isLoading) {
titleRow.children.add(Padding( titleRow.children.add(Padding(
child: JumpingDotsProgressIndicator( child: JumpingDotsProgressIndicator(
fontSize: 26.0, fontSize: 26.0,
@ -297,26 +320,52 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
); );
} }
_getErrorMessageByCode(int code, bool short) {
String message = short ? "Unknown error" : "Unknown error";
switch (code) {
case 1: {
message = short ? "Unable to connect" : "Unable to connect\n Please check your internet connection and Home Assistant instance state";
break;
}
}
return message;
}
_checkShowInfo(BuildContext context) { _checkShowInfo(BuildContext context) {
if (_fetchErrorCode > 0) { if (_errorCodeToBeShown > 0) {
String text = _getErrorMessageByCode(_fetchErrorCode, true); String message = _lastErrorMessage;
SnackBarAction action; SnackBarAction action;
switch (_fetchErrorCode) { switch (_errorCodeToBeShown) {
case 1: { case 1: {
action = SnackBarAction( action = SnackBarAction(
label: "Retry", label: "Retry",
onPressed: _refreshData, onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
);
break;
}
case 5: {
message = "Check connection settings";
action = SnackBarAction(
label: "Open",
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings');
},
);
break;
}
case 6: {
action = SnackBarAction(
label: "Settings",
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
Navigator.pushNamed(context, '/connection-settings');
},
);
break;
}
case 7: {
action = SnackBarAction(
label: "Retry",
onPressed: () {
_scaffoldKey?.currentState?.hideCurrentSnackBar();
_refreshData();
},
); );
break; break;
} }
@ -325,7 +374,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_scaffoldKey.currentState.hideCurrentSnackBar(); _scaffoldKey.currentState.hideCurrentSnackBar();
_scaffoldKey.currentState.showSnackBar( _scaffoldKey.currentState.showSnackBar(
SnackBar( SnackBar(
content: Text("$text"), content: Text("$message (code: $_errorCodeToBeShown)"),
action: action, action: action,
duration: Duration(hours: 1), duration: Duration(hours: 1),
) )
@ -363,7 +412,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
Icon( Icon(
_createMDIfromCode(MaterialDesignIcons.getCustomIconByName("mdi:home-assistant")), _createMDIfromCode(MaterialDesignIcons.getCustomIconByName("mdi:home-assistant")),
size: 100.0, size: 100.0,
color: _fetchErrorCode == 0 ? Colors.blue : Colors.redAccent, color: _errorCodeToBeShown == 0 ? Colors.blue : Colors.redAccent,
), ),
] ]
), ),

View File

@ -11,7 +11,7 @@ class ConnectionSettingsPage extends StatefulWidget {
class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> { class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _hassioDomain = ""; String _hassioDomain = "";
String _hassioPort = ""; String _hassioPort = "8123";
String _hassioPassword = ""; String _hassioPassword = "";
String _socketProtocol = "wss"; String _socketProtocol = "wss";
@ -26,20 +26,18 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
setState(() { setState(() {
_hassioDomain = prefs.getString("hassio-domain"); _hassioDomain = prefs.getString("hassio-domain");
_hassioPort = prefs.getString("hassio-port"); _hassioPort = prefs.getString("hassio-port") ?? '8123';
_hassioPassword = prefs.getString("hassio-password"); _hassioPassword = prefs.getString("hassio-password");
_socketProtocol = prefs.getString("hassio-protocol"); _socketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
}); });
} }
_saveSettings() async { _saveSettings() async {
debugPrint("Saving settings....");
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("hassio-domain", _hassioDomain); prefs.setString("hassio-domain", _hassioDomain);
prefs.setString("hassio-port", _hassioPort); prefs.setString("hassio-port", _hassioPort);
prefs.setString("hassio-password", _hassioPassword); prefs.setString("hassio-password", _hassioPassword);
prefs.setString("hassio-protocol", _socketProtocol); prefs.setString("hassio-protocol", _socketProtocol);
debugPrint("Done saving settings....");
} }
@override @override
@ -47,8 +45,10 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
return new Scaffold( return new Scaffold(
appBar: new AppBar( appBar: new AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
_saveSettings(); _saveSettings().then((r){
Navigator.pop(context); Navigator.pop(context);
});
eventBus.fire(SettingsChangedEvent(true));
}), }),
// Here we take the value from the MyHomePage object that was created by // Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title. // the App.build method, and use it to set our appbar title.