jinyus / lite_ref

A lightweight dependency injection library for Dart and Flutter.
11 stars 3 forks source link

discussion: Scoped refs with arguments. #25

Closed yehorh closed 6 months ago

yehorh commented 6 months ago

Sometimes you need reuse one Ref constructor for different screens. For example, forms for different users, different products, or any other entities.

In such cases, when using state_beacon, you could create one Ref object and make all beacons accessible through a family in it, but this leads to code redundancy and debugging complexities.

Has the option of adding an interface to scoped refs for creating them and retrieving them with arguments been considered?

jinyus commented 6 months ago

Could you show some pseudocode of how the interface would look? I'm not understanding the use-case

yehorh commented 6 months ago

To be honest, I can't suggest a specific good interface right now, I need to think it over.

But an example of how I see its use is roughly as follows.

final featureControllerRef = Ref.scoped((context, id) => FeatureController(id: id));

class FeatureController extends BeaconController {
  FeatureController({required this.id});

  final int id;
  late final foo = Beacon.writeable('');
  late final bar = Beacon.writeable('');
  late final baz = Beacon.writeable('');

  Future<void> update() async {
    final entityId = id;
    final fooValue = foo.value;
    final barValue = bar.value;
    final bazValue = baz.value;
    // do something with the values
  }
}

class FeatureScreen extends StatelessWidget {
  const FeatureScreen({required this.id, super.key});

  final int id;

  @override
  Widget build(BuildContext context) {
    final controller = featureControllerRef.of1(context, id);
    return Column(
      children: [
        Foo(id: id),
        Bar(id: id),
        Baz(id: id),
        Button(
          onPressed: controller.update,
          child: const Text('Update'),
        ),
      ]
    );
  }
}
jinyus commented 6 months ago

For that, I'd wrap the feature screen in another LiteRefScope and override the featureController:

LiteRefScope(
        overrides: {
          featureControllerRef.overrideWith((_) => FeatureController(id: id)),
        },
        onlyOverrides: true,
        child: const FeatureScreen(id: id),
      ),
    )

PS: Use B.writable() instead of Beacon.writable() when using BeaconController. I'm thinking of making this more ergonomic.

yehorh commented 6 months ago

Yes, thank you, I was considering this option. However, if my constructor can accept some parameters that I cannot obtain from the context, I am forced to declare the Ref as lazy and specify its initialization only in LiteRefScope. This might lead to future issues with accessing an uninitialized lazy variable, essentially resulting in the same issue as with Provider.

Regarding the BeaconController, that's exactly what I do, I just forgot to mention it in the example, but thank you very much for the tip.

jinyus commented 6 months ago

Yea, I realized that it's hard to get away from the not found error but I just live with it. Sometimes I will just throw an exn in the top level ref and override it above the page that uses it. Then it basically functions like Provider;

final featureControllerRef = Ref.scoped<FeatureController>((_) => throw Exception('You forgot to override the ref!'));

This hasn't bothered me much but I will try to make it more ergonomic if possible. Riverpod would use a family provider for this; I guess I could do the same.