Skip to content

Commit

Permalink
Refine event handling in android backend (#98)
Browse files Browse the repository at this point in the history
- Add 2 new enums: agent.StateEvent and agent.FailureEvent.
  We'll use them instead of string code when emitting events
  from agent to model, to ensure that model won't have an
  implicit dependency on some magic strings.

- refreshState() generates StateEvent. No matter what even we got
  from kotlin, we always check actual state we have right now and
  generate event according to that state. This way we can be sure
  that events received by model will be consistent with the state
  that it observes when calling receiverIsAlive/senderIsAlive.

- onError() translates error codes from kotlin to higher-level
  failure event codes. Model translates these error codes to
  localized error messages. UI shows these messages.
  • Loading branch information
gavv authored and Izchomatik committed Feb 21, 2025
1 parent 431d5bc commit d7ca1a5
Show file tree
Hide file tree
Showing 9 changed files with 57 additions and 17 deletions.
2 changes: 2 additions & 0 deletions lib/src/agent.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
export 'agent/android_backend.dart';
export 'agent/android_bridge.g.dart';
export 'agent/backend.dart';
export 'agent/failure_event.dart';
export 'agent/noop_backend.dart';
export 'agent/state_event.dart';
32 changes: 22 additions & 10 deletions lib/src/agent/android_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import 'package:logger/logger.dart';

import 'android_bridge.g.dart';
import 'backend.dart';
import 'failure_event.dart';
import 'state_event.dart';

/// Android-specific implementation of Backend interface.
///
Expand All @@ -29,10 +31,10 @@ class AndroidBackend implements Backend, AndroidListener {
bool senderIsAlive = false;

@override
final Event<Value<String>> stateChangeEvent = Event();
final Event<Value<StateEvent>> stateChangeEvent = Event("stateChangeEvent");

@override
final Event<Value<String>> failureEvent = Event();
final Event<Value<FailureEvent>> failureEvent = Event("failureEvent");

/// Inherited from Backend interface.
/// Invoked from model.
Expand Down Expand Up @@ -187,22 +189,30 @@ class AndroidBackend implements Backend, AndroidListener {
@override
void onEvent(AndroidServiceEvent eventCode) async {
_logger.d("Registered event: $eventCode");
await refreshState(eventCode.name);
await refreshState();
}

/// Inherited from AndroidListener interface.
/// Invoked from kotlin.
@override
void onError(AndroidServiceError errorCode) async {
_logger.d("Registered event: $errorCode");
await refreshState(errorCode.name);

await refreshState();

switch (errorCode) {
case AndroidServiceError.audioRecordFailed:
case AndroidServiceError.audioTrackFailed:
failureEvent.broadcast(Value<FailureEvent>(FailureEvent.deviceError));

case AndroidServiceError.senderConnectFailed:
case AndroidServiceError.receiverBindFailed:
failureEvent.broadcast(Value<FailureEvent>(FailureEvent.networkError));
}
}

/// Async method used in all Android service event types.
Future<void> refreshState([String? eventCode]) async {
final broadcastValue =
eventCode == null ? "Register state change" : eventCode;

Future<void> refreshState() async {
final newReceiverIsAlive = await _connector.isReceiverAlive();
if (receiverIsAlive != newReceiverIsAlive) {
_logger.d(
Expand All @@ -211,7 +221,8 @@ class AndroidBackend implements Backend, AndroidListener {

// Whenever receiver state changes, no matter how we've found out (from kotlin
// event of from finally block), we notify subscribers
stateChangeEvent.broadcast(Value(broadcastValue));
stateChangeEvent
.broadcast(Value<StateEvent>(StateEvent.receiverStateChanged));
}

final newSenderIsAlive = await _connector.isSenderAlive();
Expand All @@ -222,7 +233,8 @@ class AndroidBackend implements Backend, AndroidListener {

// Whenever sender state changes, no matter how we've found out (from kotlin
// event of from finally block), we notify subscribers
stateChangeEvent.broadcast(Value(broadcastValue));
stateChangeEvent
.broadcast(Value<StateEvent>(StateEvent.senderStateChanged));
}
}
}
10 changes: 8 additions & 2 deletions lib/src/agent/backend.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:event/event.dart';

import 'android_bridge.g.dart';
import 'failure_event.dart';
import 'state_event.dart';

// This is intended to be platform-independent interface for streaming backend,
// implemented differently on mobile and desktop.
Expand All @@ -24,8 +26,12 @@ import 'android_bridge.g.dart';
abstract class Backend {
abstract bool receiverIsAlive;
abstract bool senderIsAlive;
abstract final Event<Value<String>> stateChangeEvent;
abstract final Event<Value<String>> failureEvent;

/// Emitted when receiverIsAlive or senderIsAlive changes its value.
abstract final Event<Value<StateEvent>> stateChangeEvent;

/// Emitted when sender or receiver fails in background.
abstract final Event<Value<FailureEvent>> failureEvent;

Future<List<String>> getLocalAddresses();

Expand Down
5 changes: 5 additions & 0 deletions lib/src/agent/failure_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Event codes for failure event.
enum FailureEvent {
deviceError,
networkError,
}
6 changes: 4 additions & 2 deletions lib/src/agent/noop_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:event/event.dart';

import 'android_bridge.g.dart';
import 'backend.dart';
import 'failure_event.dart';
import 'state_event.dart';

class NoopBackend implements Backend {
@override
Expand All @@ -11,10 +13,10 @@ class NoopBackend implements Backend {
bool senderIsAlive = false;

@override
final Event<Value<String>> stateChangeEvent = Event("stateChangeEvent");
final Event<Value<StateEvent>> stateChangeEvent = Event("stateChangeEvent");

@override
final Event<Value<String>> failureEvent = Event("failureEvent");
final Event<Value<FailureEvent>> failureEvent = Event("failureEvent");

@override
Future<List<String>> getLocalAddresses() async {
Expand Down
5 changes: 5 additions & 0 deletions lib/src/agent/state_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Event codes for state change events.
enum StateEvent {
senderStateChanged,
receiverStateChanged,
}
2 changes: 1 addition & 1 deletion lib/src/model/model_root.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ModelRoot {
late final Logger logger;

/// Used to notify if the backend service has registered an error event.
final Event<Value<String>> failureEvent = Event("failureEvent");
final Event<Value<FailureEvent>> failureEvent = Event("failureEvent");

ModelRoot(Logger logger, Backend backend) {
this.receiver = Receiver(logger, backend);
Expand Down
4 changes: 4 additions & 0 deletions lib/src/ui/localization/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,9 @@

"enterIp": "Enter IP address",

"deviceError": "Audio device error",

"networkError": "Network error",

"appVersion": "v0.2.1"
}
8 changes: 6 additions & 2 deletions lib/src/ui/main_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_mobx/flutter_mobx.dart';

import '../agent.dart';
import '../model/model_root.dart';
import 'components/roc_snackbar.dart';
import 'fragments/roc_bottom_navigation_bar.dart';
Expand Down Expand Up @@ -45,8 +46,11 @@ class _MainScreenState extends State<MainScreen> {
] {
// Subscribe to model failure event (coming from backend failure event).
_modelRoot.failureEvent.subscribe((args) {
var message = args.value;
RocSnackbar.showMessage(context: context, message: 'Error: $message');
var message = switch (args.value) {
FailureEvent.deviceError => AppLocalizations.of(context)!.deviceError,
FailureEvent.networkError => AppLocalizations.of(context)!.networkError,
};
RocSnackbar.showMessage(context: context, message: message);
});
}

Expand Down

0 comments on commit d7ca1a5

Please sign in to comment.