oldredman
oldredman

Reputation: 23

A value of type 'Future<List>' can't be assigned to a variable of type 'List'

I am really newbie in Flutter and SQLite. I need to store some data got from a DB into a global variable (in this code it's a local variable just for exemplification) and I don't know:

  1. where is the best point I can do it (now I put it in the homepage's initState method);
  2. how I can store future data in a no-future variable.

Below is the method for the data extraction

class ObjectRepository {
  ObjectRepository._(); 
  static final ObjectRepository instance = ObjectRepository._();

  Future<List<Map>> select(Database db, String tableName, {List<String> fields}) async {
        sql = 'SELECT ' + (fields != null ? fields.toString() : '* ');
        sql = sql + 'FROM $tableName';
    
        final List<Map> list = await db.rawQuery(sql);
    
        return list;
  }
}

Below is where I put my future<List<Map>> into a List<Map> (I know I can't, in fact the IDE gives me this error A value of type 'Future<List<Map<dynamic, dynamic>>>' can't be assigned to a variable of type 'List<Map<dynamic, dynamic>>'. Try changing the type of the variable, or casting the right-hand type to 'List<Map<dynamic, dynamic>>'.

I even tried to cast it, but I got a similar issue at run-time "type 'Future<List<Map<dynamic, dynamic>>>' is not a subtype of type 'List<Map<dynamic, dynamic>>' in type cast".

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    super.initState();

    DBProvider.instance.openDB(globalConstants.dbName);
    List<Map> list = ObjectRepository.instance.select(DBProvider.instance.database, 'tCars') as List<Map>;
  }

  @override
  void dispose() {
    DBProvider.instance.closeDB();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CustomAppBar(title: Text('Cars')),
      drawer: CustomDrawer(),
      body: CustomBody(),
    );
  }
}

Any idea how can I resolve it? Many thanks, folks.

Upvotes: 2

Views: 9924

Answers (2)

Lakmal Fernando
Lakmal Fernando

Reputation: 1540

Error is because instant.select function returns the Future<List<Map>> but you are tring to asign it to List<Map> therefore you have to await until the future completes. but the initState is not an async function. therefore you cannot await there. but you can use then method on future (which is called when future is completed with the value). and give it the callback (action you want to perform with the value) to then method. But you have to careful to check whether the list is null or not before you using it by if statement because it is set to null initially.

    class HomePage extends StatefulWidget {
    
      HomePage({Key key}) : super(key: key);
    
      List<Map>? list;
      @override
      _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      void initState() {
        super.initState();
    
        DBProvider.instance.openDB(globalConstants.dbName);
        ObjectRepository.instance.select(DBProvider.instance.database, 'tCars').then((result){
           if(mounted) {
             setState((){ list = result});
              }else { list = result}
            });
      }
    
      @override
      void dispose() {
        DBProvider.instance.closeDB();
    
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: CustomAppBar(title: Text('Cars')),
          drawer: CustomDrawer(),
          body: CustomBody(),
        );
      }
    }

Instant of this if you are tring to use your list for a widget (eg: ListView). consider using FutureBuilder which takes future and builder function to return appropriate widget according to the state of the future you have given to it. For Example,

 FutureBuilder<List<Map>>(
   future: ObjectRepository.instance.select(DBProvider.instance.database, 'tCars') // your future here,
builder : (context, snapshot) {
       if (snapshot.hasError) return ErorrScreen();
       if (snapshot.hasData) return ListView.builder(itemCount :  snapshot.data.length,
         itemBuilder: (context, index) {
             return Text(snpshot.data[index].toString());
});
return CicularProgressIndicator();
}
)

this is the more recommended way of using futures in your UI in flutter.

Upvotes: 0

Ehsan Askari
Ehsan Askari

Reputation: 891

Reading from database is an asynchronous activity, which means the query doesn't return some data immediately. so you have to wait for the operation to complete and then assign it to a variable.

DBProvider.instance.openDB(globalConstants.dbName);
List<Map> list = await ObjectRepository.instance.select(DBProvider.instance.database, 'tCars') as List<Map>;

On the other hand, you can not use await inside initState. The solution is to create an async function that handles the process of getting data from database.

Future<List<Map>> initDB()async{
  DBProvider.instance.openDB(globalConstants.dbName);
  List<Map> list = await ObjectRepository.instance.select(DBProvider.instance.database, 'tCars') as List<Map>;
  return list;
}

finally you can call initDB inside initState

Upvotes: 1

Related Questions