Search code examples
flutterasync-awaitsupabasesupabase-flutter

Why am I getting "The operator '[]' isn't defined for the type 'Future<Map<String, dynamic>>' "


I am trying to fetch a single row of data from Supabase using the following code:

// supabase_service.dart
Future<Map<String, dynamic>> fetchTransaction(int id) async {
    return await _supabaseClient
        .from('transactions')
        .select()
        .eq('id', id)
        .single();
  }

I am calling this code from a stateless widget like so:

final int id;
final dbservice = SupabaseService();

  @override
  Widget build(BuildContext context) {
    final transaction = dbservice.fetchTransaction(id);
    // do stuff with result
      Text('TXN ID: ${transaction["id"]}'); // <---error

However, when I try to use 'transaction', I get the following error:

The operator '[]' isn't defined for the type 'Future<Map<String, dynamic>>'

I can't figure out if the error is because I'm trying to access the data incorrectly, or if it's because I need to further process the data in some way before accessing.


Solution

  • As @thenry answered, the value is loaded from the database so it won't be immediately available. That's why your variable is a Future, and why you can't access it as you'd do with a normal "now" variable.

    You indeed can't use await in the build method, as all UI building has to happen synchronously. I hope that makes sense when you think about it: you always have to render a UI even while the data is loading. At that point you'll typically want to render some UI element that shows that the data is loading.

    The simplest way to display a Future value in the UI is to wrap it in a FutureBuilder:

    Widget build(BuildContext context) {
      final transaction = dbservice.fetchTransaction(id);
        
      return FutureBuilder<List<String>?>(
          future: transaction,
          builder: (context, snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.waiting:
                return Center(
                  child: CircularProgressIndicator(),
                );
              case ConnectionState.done:
                if (snapshot.hasError) {
                  return Text('ERROR: ${snapshot.error.toString()}');
                } else {
                  return Text('TXN ID: ${snapshot.data["id"]}');
                }
              default:
                return Text('Unhandle State');
            }
          },
        ),
    }
    

    So you can see that here we handle the three main states:

    • If the data is still loading, we show a CircularProgressIndicator.
    • If there was an error, we show the error in a text box.
    • If the data was loaded, we show the value you tried to show already.

    If this is new to you, I also recommend reading: