Flutter Reactive Navigation Based on Stream

Viewed 166

Context

I'm building a bluetooth application. This application has 4 tabs, and one of them is the bluetooth tab. This tab shows the ListAllDevices page, or the DeviceData page, based on a stream value. In the ListAllDevices page I search and connect to a device. In the DeviceData page I can see some data of the device and access some other screens, lets say DeviceSettings and AnotherDeviceSettings for example.

What I want to do?

  • Interactive flow: The first time I click in bluetooth tab, I want the ListAllDevices page to open. Once there, I'll scan for some devices, and when I click to connect to one of them, it will take me to the DeviceData page. Later on, when I click in a button in the DeviceData page, it will take me to one the DeviceSettings page.

  • Reactive flow: I want to keep listening to the bluetooth state and, if it gets disabled, the entire stack of the DeviceData page must be removed and show the ListAllDevices page again (its state must be the same as they were before changing to the DeviceData page).

What I've done so far?

The interactive flow is fine:

  • I have a repository which holds the current connected device as a single value, and as a stream. Its value is updated when I click to connect to or disconnect from a device manually and when a stream notifies me that the bluetooth is disabled.

  • The main widget of the bluetooth page is the BluetoothTabPage, which has only a StreamBuilder to decide between ListAllDevices and the DeviceData based on value of the connected device.

  • The DeviceData page pushes some other pages at some point.

Abstract code:

class BluetoothTabPage extends StatefulWidget {
  const BluetoothTabPage();

  @override
  _BluetoothTabPageState createState() => _BluetoothTabPageState();
}

class _BluetoothTabPageState extends State<BluetoothTabPage> {
  StreamSubscription _bluetoothStateSubscription;

  @override
  void initState() {
    _bluetoothStateSubscription =
        BluetoothManager.instance.bluetoothState().listen((bluetoothState) {
      if (bluetoothState != const BluetoothStateState.enabled()) {
        Repository.I.setConnectedDevice(const None());
      }
    });
    super.initState();
  }

  @override
  void dispose() {
    _bluetoothStateSubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<Option<IDevice>>(
      stream: Repository.I.watchConnectedDevice,
      initialData: Repository.I.connectedDevice,
      builder: (_, snapshot) => snapshot.data.fold(
        () => const ListAllDevices(),
        (_) => const DeviceData(),
      ),
    );
  }
}

class ListAllDevices extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    connected device = some device;
  }
}

class DeviceData extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (appbar action) {
      show DeviceSettings;
    } else if (appbar another action) {
      show AnotherDeviceSettings;
    } else if (appbar action to disconnect) {
      connected device = null;
    }
  }
}

class DeviceSettings extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (app bar back arrow) pop to DeviceData;
  }
}

class AnotherDeviceSettings extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (app bar back arrow) pop to DeviceData;
  }
}

The reactive flow is where the problem lives:

  • When I do connected device = some device; in the ListAllDevices page, the repository changes, the StreamBuilder listens and shows me the DeviceData page. Fine!

  • When I do connected device = null; in the DeviceData page, the connected device in the repository changes, the StreamBuilder listens and shows me the ListAllDevices page again. So far so good!

  • When I navigate to the DeviceSettings page and disconnect the bluetooth, nothing happens, but I expected the app to take me back to the ListAllDevices page.

  • Another thing is: If click in the app bar back button in the DeviceSettingsPage page, it will show me the ListAllDevicesPage page as expected, which means the StreamBuilder is working correctly, just the pushed DeviceSettingsPage that is not being popped.

So, the overall question is: Why it is not being popped and how can I achieve this?

0 Answers
Related