0

Whenever I launch/re-launch my application on a real device the screen doesn't load or render the widgets until I make some touch inputs like scrolling the list (that is present in the home screen but not yet visible/rendered) or swiping between screens (the tabs under tabbar/pageview), without these manual touch inputs the app doesn't render these widgets automatically. It works fine on the emulator, the issue only exists on a real device for me. I found a similar thread here which might help understand the problem a bit better.

The main screen of the application fetches data from a local SQLite Database and displays it in a ListView, I use provider to fetch data and update the UI, and I use FutureBuilder to build the widgets.

I am aware that the TabBar doesn't persist state and I tried to fix it using AutomaticKeepAliveClientMixin in all my Stateful Widgets under the TabBar as shown here, but that too didn't work for me. Everything works fine for me when I'm just using a BottomNavBar or just without the TabBar or PageView.

I also tried to use PageController with BottomNavBar because I want that swipe to change tabs feature, but I'm facing the same issue again. Here's the code:

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

import '../../widgets/custom_scaffold.dart';
import './all_tasks_list.dart';
import './todays_task_list.dart';
import './task_input_field.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final PageController _pageController = PageController();

  int _activePage = 0;

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  List<String> appBarTitles = [
    DateFormat('EEE, MMM d').format(DateTime.now()),
    'Summary'
  ];

  @override
  Widget build(BuildContext context) {
    return MyScaffold(
      appBarTitle: appBarTitles[_activePage],
      body: PageView(
        physics: const BouncingScrollPhysics(
            parent: AlwaysScrollableScrollPhysics()),
        controller: _pageController,
        children: const [
          TodaysTasks(),
          AllTasksList(),
        ],
        onPageChanged: (index) {
          setState(() {
            _activePage = index;
          });
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        selectedIconTheme: const IconThemeData(size: 35),
        selectedFontSize: 0,
        currentIndex: _activePage,
        onTap: (index) {
          _pageController.animateToPage(
            index,
            duration: const Duration(milliseconds: 350),
            curve: Curves.ease,
          );
        },
        type: BottomNavigationBarType.shifting,
        items: [
          BottomNavigationBarItem(
            icon: const Icon(Icons.task),
            label: 'Today',
            backgroundColor: Theme.of(context).hintColor,
          ),
          BottomNavigationBarItem(
            icon: const Icon(Icons.history),
            label: 'History',
            backgroundColor: Theme.of(context).hintColor,
          ),
        ],
      ),
    );
  }
}

class TodaysTasks extends StatefulWidget {
  const TodaysTasks({Key? key}) : super(key: key);

  @override
  State<TodaysTasks> createState() => _TodaysTasksState();
}

class _TodaysTasksState extends State<TodaysTasks>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Column(
      children: const [
        Flexible(child: TodaysTasksList()),
        TaskInputTextField(),
      ],
    );
  }

  @override
  bool get wantKeepAlive => true;
}

all_tasks_list.dart:

// All Imports...

class AllTasksList extends StatefulWidget {
  const AllTasksList({Key? key}) : super(key: key);

  @override
  State<AllTasksList> createState() => _AllTasksListState();
}

class _AllTasksListState extends State<AllTasksList>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return FutureBuilder(
      future:
          Provider.of<TaskProvider>(context, listen: false).fetchAndSetTasks(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          List<TaskModel> listOfTasks =
              Provider.of<TaskProvider>(context).getAllTasks;
          return ListView.builder(
            itemCount: listOfTasks.length,
            itemBuilder: (context, index) => ListTile(
              title: Text(listOfTasks[index].taskTitle),
              subtitle:
                  Text(DateMethods.dateFormatter(listOfTasks[index].timeStamp)),
            ),
          );
        } else {
          return const Center(child: CircularProgressIndicator());
        }
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}

todays_task_list.dart:

// Imports...

class TodaysTasksList extends StatefulWidget {
  const TodaysTasksList({Key? key}) : super(key: key);

  @override
  State<TodaysTasksList> createState() => _TodaysTasksListState();
}

class _TodaysTasksListState extends State<TodaysTasksList>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    return FutureBuilder(
      future: Provider.of<TaskProvider>(context, listen: false)
          .fetchAndSetTodaysTasks(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          List<TaskModel> listOfTasks =
              Provider.of<TaskProvider>(context).getTasks;

          if (listOfTasks.isEmpty) {
            return const Center(
              child: Text('No tasks added today'),
            );
          } else {
            return ListView.builder(
              physics: const BouncingScrollPhysics(
                  parent: AlwaysScrollableScrollPhysics()),
              itemCount: listOfTasks.length,
              itemBuilder: (context, index) => ChangeNotifierProvider.value(
                key: ValueKey(listOfTasks[index].timeStamp.toIso8601String()),
                value: listOfTasks[index],
                child: const TaskItem(),
              ),
            );
          }
        } else {
          return const Center(child: CircularProgressIndicator());
        }
      },
    );
  }

  @override
  bool get wantKeepAlive => true;
}

class TaskItem extends StatefulWidget {
  const TaskItem({
    Key? key,
  }) : super(key: key);

  @override
  State<TaskItem> createState() => _TaskItemState();
}

class _TaskItemState extends State<TaskItem>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    final taskItem = Provider.of<TaskModel>(context, listen: false);
    return ListTile(
      title: Consumer<TaskModel>(
        builder: (context, value, _) => TaskTitle(taskItem: taskItem),
      ),
      subtitle: Text(
        DateMethods.dateFormatter(taskItem.timeStamp),
      ),
      trailing: Consumer<TaskModel>(
          builder: (context, value, child) => TrailingIcon(taskItem: taskItem)),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

class TaskTitle extends StatefulWidget {
  final TaskModel taskItem;
  const TaskTitle({Key? key, required this.taskItem}) : super(key: key);

  @override
  State<TaskTitle> createState() => _TaskTitleState();
}

class _TaskTitleState extends State<TaskTitle>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    // returns const Widgets to be displayed
    }
  }

  @override
  bool get wantKeepAlive => true;
}

class TrailingIcon extends StatefulWidget {
  final TaskModel taskItem;
  const TrailingIcon({Key? key, required this.taskItem}) : super(key: key);

  @override
  State<TrailingIcon> createState() => _TrailingIconState();
}

class _TrailingIconState extends State<TrailingIcon>
    with AutomaticKeepAliveClientMixin {
  @override
  Widget build(BuildContext context) {
    super.build(context);
    // returns const Widgets to be displayed
    }
  }

  @override
  bool get wantKeepAlive => true;

  void deleteTask(BuildContext context, String taskId) {
    Provider.of<TaskProvider>(context, listen: false).deleteTask(taskId);
  }
}

task_input_field.dart:

// Imports...

class TaskInputTextField extends StatefulWidget {
  const TaskInputTextField({Key? key}) : super(key: key);

  @override
  State<TaskInputTextField> createState() => _TaskInputTextFieldState();
}

class _TaskInputTextFieldState extends State<TaskInputTextField>
    with AutomaticKeepAliveClientMixin {
  final TextEditingController _taskController = TextEditingController();

  @override
  void dispose() {
    _taskController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 10),
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        border: Border.all(color: Theme.of(context).hintColor, width: 2),
        borderRadius: BorderRadius.circular(15),
      ),
      child: TextField(
        // TextField Decoration code
        ),
        //Just some usual TextField stuff
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;

  void addTask() {
    if (_taskController.text.isEmpty) {
      finishingUp();
      return;
    }
    TaskModel taskData =
        TaskModel(taskTitle: _taskController.text, timeStamp: DateTime.now());
    Provider.of<TaskProvider>(context, listen: false).addTask(taskData);
    finishingUp();
  }

  void finishingUp() {
    _taskController.clear();
    FocusScope.of(context).unfocus();
  }
}
Novice Coder
  • 219
  • 3
  • 6

1 Answers1

0

I encoutered a similar problem. In my case, the PageView crashed only in release mode. I followed this GitHub thread and the issue seemed to go away.

In the thread there is a solution of delaying the runApp() function by a few milliseconds as shown.

Before trying this solution run flutter doctor -v and ensure that you are on the stable channel and everything is up to date.

Initial code.

void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
    MyMaterialApp(home:somePageView())
  );
}

Code after fix

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Future.delayed(const Duration(milliseconds: 200));
  runApp(
    MyMaterialApp(home:somePageView())
    ),
  );
}

Please note that this is not the best solution but it fixes the problem for most cases.

Also making the main() function async has different views as shown in StackOverflow Thread