admin管理员组

文章数量:1355564

I'm using Flutter Provider, and I'm trying to figure out how to provide my dependencies to a dialog's context while maintaining the lazy initialization.

See my example below where dependencies are provided to the dialogs context, but .read() initializes the dependencies, thus they are no longer lazily initialized in the dialog itself at the moment they are first used within the dialog itself.

showDialog(
  context: context,
  builder: (_) {
    return MultiProvider(
      providers: <SingleChildWidget>[
        Provider<MyViewModel>.value(
          value: context.read<MyViewModel>(),
        ),
        BlocProvider<UploadImageCubit>.value(
          value: context.read<UploadImageCubit>(),
        ),
      ],
      child: InvalidImageSizeDialogWidget(
        assetPath: path,
        bytes: file.bytes!,
        originalWidth: _originalWidth,
        originalHeight: _originalHeight,
      ),
    );
  },
);

I am aware that I can solve this problem lifting up the state to app scope by wrapping the Material app, but this is not what I want/need. As these dependencies are scoped by Route instead and they should not be available for the entire app.

What I need to know is how I provide dependencies that are declared higher up the widget tree to the context of a dialog with Provider while maintaining lazy initialization?

I'm using Flutter Provider, and I'm trying to figure out how to provide my dependencies to a dialog's context while maintaining the lazy initialization.

See my example below where dependencies are provided to the dialogs context, but .read() initializes the dependencies, thus they are no longer lazily initialized in the dialog itself at the moment they are first used within the dialog itself.

showDialog(
  context: context,
  builder: (_) {
    return MultiProvider(
      providers: <SingleChildWidget>[
        Provider<MyViewModel>.value(
          value: context.read<MyViewModel>(),
        ),
        BlocProvider<UploadImageCubit>.value(
          value: context.read<UploadImageCubit>(),
        ),
      ],
      child: InvalidImageSizeDialogWidget(
        assetPath: path,
        bytes: file.bytes!,
        originalWidth: _originalWidth,
        originalHeight: _originalHeight,
      ),
    );
  },
);

I am aware that I can solve this problem lifting up the state to app scope by wrapping the Material app, but this is not what I want/need. As these dependencies are scoped by Route instead and they should not be available for the entire app.

What I need to know is how I provide dependencies that are declared higher up the widget tree to the context of a dialog with Provider while maintaining lazy initialization?

Share Improve this question edited Mar 31 at 15:10 anonymous-dev asked Mar 28 at 14:14 anonymous-devanonymous-dev 3,52112 gold badges66 silver badges138 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 5 +50

Reproviding what we passed with Provider is as easy as just using the Provider default constructor again. We don't set the dispose property, because we don't want this to clean-up. It's up to the parent (previous context's) provider.

The BlocProvider is trickier. The default constructor with the create param has no way of disabling the disposal mechanism. But we can take a look inside its implementation.

We see that it's using just Provider.of inside of its of method, so we're safe providing it down the tree with the Provider.

  static T of<T extends StateStreamableSource<Object?>>(
    BuildContext context, {
    bool listen = false,
  }) {
    try {
      return Provider.of<T>(context, listen: listen);
      // ...

But the trick is to make it also resemble its behavior, and BlocProvider will make widgets depending on it rebuild when the bloc/cubit emits.

We also see that it provides the bloc/cubit using an InheritedProvider, where the only unordinary thing is the startListening parameter.

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
        // ...
        return InheritedProvider<T>(
            create: _create,
            dispose: (_, bloc) => bloc.close(),
            startListening: _startListening,
            lazy: lazy,
            child: child,
          );
  }

We can make a custom class that will pass the correct callback there and use it from there on.

import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

class BlocReprovider<T extends StateStreamableSource<Object?>>
    extends SingleChildStatelessWidget {
  const BlocReprovider({
    required T Function(BuildContext context) read,
    super.key,
    super.child,
  })  : _read = read;

  final T Function(BuildContext context) _read;

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    assert(
      child != null,
      '$runtimeType used outside of MultiBlocProvider must specify a child',
    );
    return InheritedProvider<T>(
      create: _read,
      // don't set dispose
      startListening: _startListening,
      lazy: true,
      child: child,
    );
  }

  static VoidCallback _startListening(
    InheritedContext<StateStreamable<dynamic>?> e,
    StateStreamable<dynamic> value,
  ) {
    final subscription = value.stream.listen(
      (dynamic _) => e.markNeedsNotifyDependents(),
    );
    return subscription.cancel;
  }
}

// ...

showDialog(
  context: context,
  builder: (_) {
    return MultiProvider(
      providers: <SingleChildWidget>[
        Provider<MyViewModel>(
          create: (context) => context.read<MyViewModel>(),
        ),
        BlocReprovider<UploadImageCubit>(
          read: (context) => context.read<UploadImageCubit>(),
        ),
      ],
      child: InvalidImageSizeDialogWidget(
        assetPath: path,
        bytes: file.bytes!,
        originalWidth: _originalWidth,
        originalHeight: _originalHeight,
      ),
    );
  },
);

Below is the full example ready to be run on https://dartpad.dev

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

class ACubit extends Cubit<bool> {
  ACubit() : super(true) {
    print('Instantiating ACubit');
  }
}

class AModel {
  AModel() {
    print('Instantiating AModel');
  }
}

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        body: MultiProvider(
          providers: [
            BlocProvider<ACubit>(create: (context) => ACubit()),
            Provider<AModel>(create: (_) => AModel()),
          ],
          child: Builder(
            builder:
                (context) => ElevatedButton(
                  child: Text('Show dialog'),
                  onPressed: () {
                    final outerContext = context;
                    showDialog(
                      context: context,
                      builder:
                          (context) => MultiProvider(
                            providers: [
                              Provider<AModel>(
                                create: (_) => outerContext.read<AModel>(),
                              ),
                              BlocReprovider<ACubit>(
                                read: (_) => outerContext.read<ACubit>(),
                              ),
                            ],
                            child: Builder(
                              builder:
                                  (context) => ElevatedButton(
                                    child: Text('Print cubit and model value'),
                                    onPressed: () {
                                      print(context.read<ACubit>().state);
                                      print(context.read<AModel>());
                                    },
                                  ),
                            ),
                          ),
                    );
                  },
                ),
          ),
        ),
      ),
    ),
  );
}

class BlocReprovider<T extends StateStreamableSource<Object?>>
    extends SingleChildStatelessWidget {
  const BlocReprovider({
    required T Function(BuildContext context) read,
    super.key,
    super.child,
  }) : _read = read;

  final T Function(BuildContext context) _read;

  @override
  Widget buildWithChild(BuildContext context, Widget? child) {
    assert(
      child != null,
      '$runtimeType used outside of MultiBlocProvider must specify a child',
    );
    return InheritedProvider<T>(
      create: _read,
      // don't set dispose
      startListening: _startListening,
      lazy: true,
      child: child,
    );
  }

  static VoidCallback _startListening(
    InheritedContext<StateStreamable<dynamic>?> e,
    StateStreamable<dynamic> value,
  ) {
    final subscription = value.stream.listen(
      (dynamic _) => e.markNeedsNotifyDependents(),
    );
    return subscription.cancel;
  }
}

本文标签: flutterHow to lazily reprovide dependencies to dialogsStack Overflow