1

I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.

The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.

The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.

class Bloc {
  final UserReposiotry _userReposiotry;
  final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);

  Bloc(this._userReposiotry) {
    _getActiveUsersCount();
  }

  void _getActiveUsersCount() async {
    final response = await _userReposiotry.getActiveUsersCount();
    _activeUsersCount.add(response.data);
  }

  ValueStream<int> get activeUsersCount => _activeUsersCount.stream;

  void dispose() async {
    await _activeUsersCount.drain(0);
    _activeUsersCount.close();
  }
}

class StatefulScreen extends StatefulWidget {
  final Bloc bloc;

  const StatefulScreen({Key? key, required this.bloc}) : super(key: key);

  @override
  State<StatefulScreen> createState() => _StatefulScreenState();
}

class _StatefulScreenState extends State<StatefulScreen> {
  @override
  Widget build(BuildContext context) {
    final stream = widget.bloc.activeUsersCount;
    return StreamBuilder<int>(
      stream: stream,
      initialData: stream.value,
      builder: (context, snapshot) {
        return Text(snapshot.data.toString());
      }
    );
  }

  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

I have the following doubts regarding this approach.

  1. StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
  2. I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
  3. I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
  4. About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:

You cannot close the subject while items are being added from addStream

Thanks for your time.

Gilgamesh
  • 197
  • 2
  • 13
  • did you find an answer? im interested – Kami Tzayig Nov 13 '22 at 07:58
  • @KamiTzayig I moved from that, too much boilerplate, I'm now using **Riverpod** and its code gen lib plus **Freezed** for the **State** models, way less boilerplate, every class can be reactive without extra work, and non UI or Flutter stuff in the business logic (Repository, Service etc). – Gilgamesh Dec 28 '22 at 23:35

0 Answers0