Search code examples
flutterdartinitialization

How to ensure code executes in proper order? Flutter


  1. I am getting error LateInitializationError: Field 'sorting' has not been initialized. In my code I need to ensure that build executes after every function in init is done. Init of sorting happens in initializeSorting() function, but apparently build is executes before sorting is initialized.
  2. Also I do not know why but prints of tasks prints empty lists [] which they shouldn't, meanwhile user and taskTitle prints are ok. I am passing the data from another widget like this: TaskSortWidget(onSort: sortTasks, user: user, taskTitle: taskTitle, tasks: tasks). Already tried approaches with nullables but didn't work. Completely no idea why both widget.tasks and tasks are empty.
  3. I will highly appreciate any help.

FULL WIDGET_SORT CODDE

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:taskease/classes/task.dart';

enum SortOrder {
  Ascending,
  Descending,
}

enum TaskSortingCriteria {
  Priority,
  DueDate,
  FromDate,
}

class TaskSortWidget extends StatefulWidget {
  final Function(List<Task>) onSort;
  final String user;
  final String taskTitle;
  final List<Task> tasks;

  TaskSortWidget(
      {required this.onSort,
      required this.user,
      required this.taskTitle,
      required this.tasks});

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

class _TaskSortWidgetState extends State<TaskSortWidget> {
  bool loading = false;
  late List<Task> tasks;
  late CollectionReference _collectionRef;
  late final String taskTitle;
  late final String user;
  late Map<String, dynamic> sorting;

  void initState() {
    super.initState();
    setState(() {
      user = widget.user;
      taskTitle = widget.taskTitle;
    });
    tasks = widget.tasks;
    _collectionRef = FirebaseFirestore.instance
        .collection('Users')
        .doc(user)
        .collection('Tasks');
    initializeSorting();
  }

  Future<void> initializeSorting() async {
    setState(() {
      loading = true;
    });
    DocumentSnapshot querySnapshot = await _collectionRef.doc(taskTitle).get();

    if (querySnapshot.exists) {
      Map<String, dynamic> data = querySnapshot.data() as Map<String, dynamic>;
      setState(() {
        sorting = data['sorting'];

      });
    } else {
      print(
          '(sort widget, this message should never show!) Sorting does not exist. Creating one...'); //this else will never execute
      await FirebaseFirestore.instance
          .collection('Users')
          .doc(user)
          .collection('Tasks')
          .doc(taskTitle)
          .set({
        'sorting': {
          '_currentSortOrderPriority': {2, 'Ascending'},
          '_currentSortOrderDueDate': {0, 'Ascending'},
          '_currentSortOrderFromDate': {1, 'Ascending'},
        }
      }, SetOptions(merge: true));
    }
    setState(() {
      loading = false;
    });
    sortTasks();
  }

  void sortTasks() {
    for (int i = 0; i < sorting.length; i++) {
      for (var entry in sorting.entries) {
        if (entry.value[0] == i) {
          SortOrder sortOrder = entry.value[1] == 'Ascending'
              ? SortOrder.Ascending
              : SortOrder.Descending;
          switch (entry.key) {
            case '_currentSortOrderPriority':
              tasks.sort((a, b) =>
                  a.priority.index.compareTo(b.priority.index) *
                  (sortOrder == SortOrder.Ascending ? 1 : -1));
              break;
            case '_currentSortOrderDueDate':
              tasks.sort((a, b) =>
                  a.dueDate!.compareTo(b.dueDate!) *
                  (sortOrder == SortOrder.Ascending ? 1 : -1));
              break;
            case '_currentSortOrderFromDate':
              tasks.sort((a, b) =>
                  a.fromDate!.compareTo(b.fromDate!) *
                  (sortOrder == SortOrder.Ascending ? 1 : -1));
              break;
          }
        }
      }
    }
  }

  void _cycleSortOrder(TaskSortingCriteria sortingCriteria) {
    setState(() {
      switch (sortingCriteria) {
        case TaskSortingCriteria.Priority:
          sorting['_currentSortOrderPriority'][1] =
              sorting['_currentSortOrderPriority'][1] == 'Ascending'
                  ? 'Descending'
                  : 'Ascending';
          switch (sorting['_currentSortOrderPriority'][0]) {
            case 0:
              sorting['_currentSortOrderPriority'][0] = 2;
              sorting['_currentSortOrderDueDate'][0]--;
              sorting['_currentSortOrderFromDate'][0]--;
              break;
            case 1:
              sorting['_currentSortOrderPriority'][0] = 2;
              if (sorting['_currentSortOrderDueDate'][0] == 2)
                sorting['_currentSortOrderDueDate'][0] = 1;
              else
                sorting['_currentSortOrderFromDate'][0] = 1;
              break;
            case 2:
              break;
          }
          break;
        case TaskSortingCriteria.DueDate:
          sorting['_currentSortOrderDueDate'][1] =
              sorting['_currentSortOrderDueDate'][1] == 'Ascending'
                  ? 'Descending'
                  : 'Ascending';
          switch (sorting['_currentSortOrderDueDate'][0]) {
            case 0:
              sorting['_currentSortOrderDueDate'][0] = 2;
              sorting['_currentSortOrderPriority'][0]--;
              sorting['_currentSortOrderFromDate'][0]--;
              break;
            case 1:
              sorting['_currentSortOrderDueDate'][0] = 2;
              if (sorting['_currentSortOrderPriority'][0] == 2)
                sorting['_currentSortOrderPriority'][0] = 1;
              else
                sorting['_currentSortOrderFromDate'][0] = 1;
              break;
            case 2:
              break;
          }
          break;
        case TaskSortingCriteria.FromDate:
          sorting['_currentSortOrderFromDate'][1] =
              sorting['_currentSortOrderFromDate'][1] == 'Ascending'
                  ? 'Descending'
                  : 'Ascending';
          switch (sorting['_currentSortOrderFromDate'][0]) {
            case 0:
              sorting['_currentSortOrderFromDate'][0] = 2;
              sorting['_currentSortOrderPriority'][0]--;
              sorting['_currentSortOrderDueDate'][0]--;
              break;
            case 1:
              sorting['_currentSortOrderFromDate'][0] = 2;
              if (sorting['_currentSortOrderPriority'][0] == 2)
                sorting['_currentSortOrderPriority'][0] = 1;
              else
                sorting['_currentSortOrderDueDate'][0] = 1;
              break;
            case 2:
              break;
          }
          break;
      }
    });
    updateSorting();
    sortTasks();
    widget.onSort(tasks);
  }

  Future<void> updateSorting() async {
    await _collectionRef.doc(taskTitle).set({
      'sorting': toJson(),
    }, SetOptions(merge: true));
  }

  Map<String, dynamic> toJson() {
    return {
      '_currentSortOrderPriority': {
        sorting['_currentSortOrderPriority'][0],
        sorting['_currentSortOrderPriority'][1],
      },
      '_currentSortOrderDueDate': {
        sorting['_currentSortOrderDueDate'][0],
        sorting['_currentSortOrderDueDate'][1],
      },
      '_currentSortOrderFromDate': {
        sorting['_currentSortOrderFromDate'][0],
        sorting['_currentSortOrderFromDate'][1],
      },
    };
  }

  Widget _buildSortButton(String label, TaskSortingCriteria sortingCriteria) {
    print(sorting);
    IconData icon;
    switch (sortingCriteria) {
      case TaskSortingCriteria.Priority:
        icon = sorting['_currentSortOrderPriority'][1] == SortOrder.Ascending
            ? Icons.arrow_upward
            : Icons.arrow_downward;
        break;
      case TaskSortingCriteria.DueDate:
        icon = sorting['_currentSortOrderDueDate'][1] == SortOrder.Ascending
            ? Icons.arrow_upward
            : Icons.arrow_downward;
        break;
      case TaskSortingCriteria.FromDate:
        icon = sorting['_currentSortOrderFromDate'][1] == SortOrder.Ascending
            ? Icons.arrow_upward
            : Icons.arrow_downward;
        break;
      default:
        print('error');
        icon = Icons.error;
    }

    return ElevatedButton.icon(
      onPressed: () {
        print('sorting by $sortingCriteria');
        _cycleSortOrder(sortingCriteria);
      },
      icon: Icon(icon),
      label: Text(label),
    );
  }

  @override
  Widget build(BuildContext context) {
    setState(() {
      tasks = widget.tasks;
    });
    return loading
        ? Center(child: CircularProgressIndicator())
        :
      Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        _buildSortButton('Sort by Priority', TaskSortingCriteria.Priority),
        SizedBox(width: 6),
        _buildSortButton('Sort by Due Date', TaskSortingCriteria.DueDate),
        SizedBox(width: 6),
        _buildSortButton('Sort by From Date', TaskSortingCriteria.FromDate),
      ],
    );
  }
}

WIDGET_SORT CALL FROM ANOTHER WIDGET

class TasksList extends StatefulWidget {
  late final String user;
  late final String taskTitle;

  TasksList({required this.user, required this.taskTitle});

  @override
  State<TasksList> createState() => _TasksListState();
}

class _TasksListState extends State<TasksList> {
  List<Task> tasks = [];
  Set<int> usedIds = Set();
  late String taskTitle;
  late final String user;
  late CollectionReference _collectionRef;
  @override
  void initState() {
    super.initState();
    user = widget.user;
    taskTitle = widget.taskTitle;

    _collectionRef = FirebaseFirestore.instance
        .collection('Users')
        .doc(user)
        .collection('Tasks');

    initializeData();
    initializeSorting();
  }

  Future<void> initializeSorting() async {
DocumentSnapshot querySnapshot = await _collectionRef.doc(taskTitle).get();

    if (!querySnapshot.exists) {
      print('Sorting does not exist. Creating one...');
      await FirebaseFirestore.instance
          .collection('Users')
          .doc(user)
          .collection('Tasks').doc(taskTitle).set
        ({
        'sorting': {
          '_currentSortOrderPriority': {2, 'Ascending'},
          '_currentSortOrderDueDate': {0, 'Ascending'},
          '_currentSortOrderFromDate': {1, 'Ascending'},
        }
      }, SetOptions(merge: true));
    }
}

  Future<void> initializeData() async {
    List<Task> fetchedTasks = await initializeTasks();
    initializeUsedIds(fetchedTasks);
  }

  void initializeUsedIds(List<Task> tasks) {
    for (Task task in tasks) {
      usedIds.add(task.id);
    }
  }

  Future<List<Task>> initializeTasks() async {...}

  Future<List<Task>> getDailyTasks() async {...}

  void sortTasks(List<Task> sortedTasks){
    setState(() {
      this.tasks = sortedTasks;
    });
  }

 @override
  Widget build(BuildContext context) {
    // print(tasks);
    return Scaffold(
      appBar: AppBar(
        title: Text(taskTitle),
      ),
      body: Column(
        children: [
          TaskSortWidget(onSort: sortTasks, user: user, taskTitle: taskTitle, tasks: tasks), //calling task sort
          Expanded(
            child: ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (BuildContext context, int index) {
                if (index == tasks.length) {
                  return Container();
                }
    //rest of code here
    }

``

Solution

  • I can't see your full build code but I am assuming you are calling values that are meant to be fetched from your database in your build before the Future function is finished.

    In this case you need to display your build conditionally when it is completed.

    bool loading = false;
    
    Future<void> initializeSorting() async {
          setState((){
            loading = true;
          });
        DocumentSnapshot querySnapshot = await _collectionRef.doc(taskTitle).get();
        if (querySnapshot.exists) {
          Map<String, dynamic> data = querySnapshot.data() as Map<String, dynamic>;
          setState(() {
            sorting = data['sorting']; //sorting initializing here
          });
        }
          setState((){
            loading = false;
          });
      }
    
    @override
      Widget build(BuildContext context) {
        print('build starts');
        return loading 
            ? CircularProgressIndicator() 
            : Row(...);
      }