Flutter: How to make an object accessible to other widgets without passing

38 Views Asked by At

I am building an application focusing on reminders. From the list of reminders present on the homepage, a reminderSection widget is shown. The reminderSection has an attribute thisReminder. I want thisReminder to be accessible to all of the descendant widgets of thisReminder. So that they can use thisReminder and I don't have to pass it around.

What I've done is use Riverpod. I wrapped myApp with ProviderScope and defined the provider in the initState of reminderSection like this:

final thisReminderProvider = Provider<Reminder>((ref) => widget.thisReminder);

And I want to use it in another widget, for example, reminderFormSection which is just where the title and dateTime of the reminder is set. I did the necessary changes to the class definition and tried accessing the provider in the initState like this:

class ReminderFormSection extends ConsumerStatefulWidget {

  const ReminderFormSection({
    super.key,
  });

  @override
  ConsumerState<ReminderFormSection> createState() => _ReminderFormSectionState();
}

class _ReminderFormSectionState extends ConsumerState<ReminderFormSection> {
  late Reminder thisReminder;

  @override
  void initState() {

    thisReminder = ref.read(thisReminderProvider);
    super.initState();
  }
}

The problem is that I'm getting the error that thisReminderProvider is undefined. I think I need to define the provider outside the class and the reminderSection file's path to the reminderFormSection file. But how do I define the provider outside the class if the object I want to provide is a class attribute?

3

There are 3 best solutions below

2
pcba-dev On BEST ANSWER

It seems what you are looking for it to be able to change the reminder that is selected from one part of your code, and be able to get the current value in another widget (ReminderFormSection).

Providers shall always be global variables. In your case, since you want to be able to change the state you shall use a "Notifier":

final reminderProvider = NotifierProvider<ReminderNotifier, Reminder?>(ReminderNotifier.new);

final class ReminderNotifier extends Notifier<Reminder?> {
  @override
  Reminder? build() {
    return null;
  }

  /// Function used to change the selected reminder, which will notify any widgets watching the Provider.
  void select(final Reminder reminder) {
    state = reminder;
  }
}

Wherever you are selecting the reminder, use the notifier of the provider:

final reminderNotifier = ref.read(reminderProvider.notifier);
// ...
reminderNotifier.select(reminder);

Then, in widgets where you want to access to the currently selected reminder, you shall watch the provided value.

class ReminderFormSection extends ConsumerWidget {

  @override
  Widget build(BuildContext context, WdigetRef ref) {
    final Reminder? reminder = ref.watch(reminderProvider);
0
AndroDevs On

Declare your provider outside of your ReminderForm or ReminderFormState section

final myProvider = Provider<String>((_) => 'John Doe');

class MyProviderClient extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // watch your provider here
    final providerValue = ref.watch(myProvider);
    return Text(providerValue);
  }
}

if you have a ConsumerStatefulWidget

@override
  void initState() {
  var providerValue = ref.read(myProvider);
}
0
Paras Valera On

Seems the issue lies with defining the provider within initState of reminderSection. Providers in Riverpod should be defined outside of widgets and accessed throughout the widget tree. Here's how to achieve this and make thisReminder accessible to all descendants:

  1. Define the Provider Outside reminderSection:

Create a separate file (e.g., reminder_providers.dart) to house your providers. Define thisReminderProvider as a global final variable:

// reminder_providers.dart
final thisReminderProvider = Provider<Reminder>((ref) => throw UnimplementedError());
  1. Pass thisReminder as an Argument:

Instead of using a provider within reminderSection, modify it to receive thisReminder as an argument in its constructor:

// reminder_section.dart
class ReminderSection extends StatelessWidget {
  final Reminder thisReminder;

  const ReminderSection({
    super.key,
    required this.thisReminder,
  });

  // ... rest of your widget implementation
}
  1. Provide thisReminder in MyApp:

Wrap your MyApp with ProviderScope. In the build method of MyApp, provide thisReminder using Provider.value:

// main.dart
void main() {
  runApp(
    ProviderScope(
      child: MyApp(
        // Access your reminder data here and pass it to ReminderSection
        thisReminder: Reminder(/* Initialize your reminder object */),
      ),
    ),
  );
}

class MyApp extends StatelessWidget {
  final Reminder thisReminder;

  const MyApp({super.key, required this.thisReminder});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // ...
      home: ReminderSection(thisReminder: thisReminder),
    );
  }
}

Now, inside reminderFormSection, you can directly access thisReminder via the Consumer widget and the ref.watch method:

class ReminderFormSection extends ConsumerWidget {
const ReminderFormSection({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final thisReminder = ref.watch(thisReminderProvider);

    // Use thisReminder in your form section

    return // ...;
  }
}