skip to Main Content

I’m trying to use ".when" method on a provider I created but I got "The method when isn’t defined for the type Future"

I would like to use a service class for all my bluetooth method.

part 'bluetooth_manager.g.dart';

@riverpod
class BluetoothService extends _$BluetoothService {
  @override
  void build() {}

  Future<List<Device>> discoverDevices() async {
    //Scan devices
    await Future.delayed(const Duration(seconds: 5));
  }
}

View:

class ModulesView extends ConsumerWidget {
  const ModulesView({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {

    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12),
        child: ref.watch(bluetoothServiceProvider.notifier).discoverDevices().when(
              data: (data) => modulesFound(ref, data),
              loading: () => loading(),
              error: (error, stacktrace) => ErrorView(errorMessage: "errorMessage", retryFunction: () {}),
            ),
      ),
    );
  }
}

2

Answers


  1. In your code, discoverDevices is just a regular function. If you need to expose its value so that you can "watch" it, make it the returned value of the build method. Other methods that don’t need their values to be watched can stay in the notifier. You can call them and get their values imperatively, for example in a button callback.

    @riverpod
    class DiscoveredDevices extends _$DiscoveredDevices {
      @override
      Future<List<Device>> build() {
        // ...
        return aListOfDevices;
      }
    
      Future<void> disconnectDevice(id) async {
        // ...
      }
    
      // An example of how you can still have a method that returns some value.
      // In this scenario, you may call this method imperatively and wait for
      // its future to complete, and then use the value maybe to show a snackbar
      // or something
      Future<bool> connectDevice(id) async {
        // ...
        return true;
      }
    }
    

    In your widget, you can use it like this (I use explicit type annotations for clarity):

    class ExampleScreen extends ConsumerWidget {
      const ExampleScreen({super.key});
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final AsyncValue<List<Device>> discoveredDevices = ref.watch(discoveredDevicesProvider);
    
        return Scaffold(
          body: discoveredDevices.when(
            loading: () => const Center(child: CircularProgressIndicator()),
            error: (_, __) => const Center(child: Text('An error occured')),
            data: (List<Device> data) => ListView.builder(
              // ...
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () async {
              // This value is not being watched, you can call it like this just to get
              // the value every time the button is pressed
              final isSuccess = await ref.read(discoveredDevicesProvider.notifier).connectDevice(id);
              // ...
            },
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    

    Generally, it should follow the pattern from this example.

    Login or Signup to reply.
  2. final bluetoothServiceProvider = Provider<BluetoothService>((_) {
      return BluetoothService();
    });
    

    If you need a value to update the UI, you should wrap the discoverDevices method with a FutureProvider

    final discorverDeviceProvider = FutureProvider.autoDispose<List<Device>>((ref) {
      final bService = ref.read(bluetoothServiceProvider);
    
      return bService.discoverDevices();
    });
    

    then you should be able to watch in the builder

    class ModulesView extends ConsumerWidget {
      const ModulesView({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final discorverDevice = ref.watch(discorverDeviceProvider);
    
        return Scaffold(
          body: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12),
            child: discorverDevice.when(
                  data: (data) => modulesFound(ref, data),
                  loading: () => loading(),
                  error: (error, stacktrace) => ErrorView(errorMessage: "errorMessage", retryFunction: () {}),
                ),
          ),
        );
      }
    }
    

    otherwise you can call bluetoothServiceProvider directly if you have other method that does not require UI changes

    ref.read(bluetoothServiceProvider).doSomething();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search