Andy
Andy

Reputation: 640

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.

Upvotes: 0

Views: 46

Answers (2)

thenry
thenry

Reputation: 368

You have defined fetchTransaction(int id) as a Future function so when calling it you'll have to add an await statement to reference its return result.

For example,

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

This should allow you to access the key "id" in the returned map, if it exists.

Upvotes: 0

Frank van Puffelen
Frank van Puffelen

Reputation: 599706

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:

Upvotes: 2

Related Questions