Tutorials

Snippets

Search

Tags

Subscribe on Youtube

Use RxDart in Flutter to synchronize UI

Cover image

Let's say you have an app that has a row of buttons. When one of those buttons are tapped you want the tapped one to grow and the rest to shrink. To achieve this functionality we can use a behaviour subject from RxDart and get_it to get tat subject where it's needed. This functionality can be used when:

  • You have a row of buttons that you want to de-activate when one is selected and only have one active
  • You have a custom toolbar that you want to react to a new item being selected
  • You have multiple UI elements that you want to synchronize in the visible UI to a single event

Add the packages to your pubspec

    ... 

    get_it: ^1.0.3
    rxdart: ^0.21.0

    ...

Create a service_locator.dart file in the lib folder.

import 'package:get_it/get_it.dart';

import 'package:connected_buttons/services/button_messagebus.dart';

GetIt locator = GetIt();

void setupLocator() {
  locator.registerSingleton(ButtonMessagebus());
}

Create a new folder called services. Under services create a file called button_message_bus.dart. This "Service" will provide a pipeline where you can place Id's and listen for id's. We'll be using a BehaviourSubject from RxDart which will always broadcast the last value on the stream if you subscribe and all emitted values there after. We'll expose the stream as idStream and we'll add a function that we can place ID's onto the stream with. When a button is tapped all it will do is place an id on the stream. Nothing else.

The button will already be registered to listen to the stream so it will react accordingly.

import 'package:rxdart/rxdart.dart';

class ButtonMessagebus {
  BehaviorSubject<int> _buttonIdSubject = BehaviorSubject<int>.seeded(-1);

  Stream<int> get idStream => _buttonIdSubject.stream;

  void broadcastId(int id) {
    _buttonIdSubject.add(id);
  }
}

Create a new folder called widgets. In that folder create a new file called connected_button.dart. It'll be a stateful widget that changes it's width and height depending on the id coming from the stream. If the ID matches we want to grow, else we shrink. We'll use the locator to get our MessageBus and subscribe in the initState function.

iimport 'dart:async';

import 'package:connected_buttons/services/button_messagebus.dart';
import 'package:flutter/material.dart';
import '../service_locator.dart';

class ConnectedButton extends StatefulWidget {
  final int id;

  ConnectedButton({ this.id});

  
  _ConnectedButtonState createState() => _ConnectedButtonState();
}

class _ConnectedButtonState extends State<ConnectedButton> {
  bool _active = false;
  double _size = 100;

  ButtonMessagebus _messageBus = locator<ButtonMessagebus>();
  StreamSubscription<int> messageSubscription;

  
  void initState() {
    // Listen for Id received
    messageSubscription = _messageBus.idStream.listen(_idReceived);
    super.initState();
  }

  void _idReceived(int id) {
     setState(() {
        if (id == widget.id) {
          _active = true;
          _size = 140;
        } else {
          _active = false;
          _size = 100;
        }
      });
  }

  
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // Broadcast Id
        _messageBus.broadcastId(widget.id);
      },
      child: _buttonUi,
    );
  }

  
  void dispose() {
    super.dispose();
    messageSubscription.cancel();
  }

  Widget get _buttonUi => AnimatedContainer(
        curve: Curves.easeIn,
        width: _size,
        height: _size,
        alignment: Alignment.center,
        decoration: BoxDecoration(
            color: Colors.grey[500], borderRadius: BorderRadius.circular(10.0)),
        duration: Duration(milliseconds: 150),
        child: Text(
          'id: ${widget.id} -> ${_active.toString()}',
          style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
        ),
      );
}

The important part of the code above is in the initState function where you subscribe to the id's being emitted. This is where you can perform your custom logic. Lastly, go to the main.dart file setup the locator. Put three or more buttons into the view and click away.

import 'package:flutter/material.dart';
import 'service_locator.dart';
import './widgets/connected_button.dart';

void main() {
  setupLocator();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
            backgroundColor: Colors.grey[800],
            body: Column(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                ConnectedButton(id: 0),
                ConnectedButton(id: 1),
                ConnectedButton(id: 2)
              ],
            )));
  }
}

You can listen to the id being broadcast anywhere in your app. All you do is register to the idStream and add your logic you want to run when an id is emitted.

Checkout and subscribe to my Youtube Channel. Follow me on Instagram for snippets and day-to-day programming. Checkout all the other snippets here. You might find some more Flutter magic.

Also check out

Cover image

A Complete Guide to Flutter Streams

In this tutorial we cover the basics of Streams and how to manage them

Link
Cover image

Build a User Feedback App in Flutter and Firebase

In this Flutter guide I show you how to connect your app to Firestore and display a list of data with real time updates.

Link
Cover image

Building a Realtime Stats monitor in Flutter

Let's build a live stats monitor mobile app for a firebase service using Flutter.

Link