Search code examples
flutterdarttabbar

Call build on Text widget when I change tab


I want to change the title in AppBar, when when I switch from one tab to another. In my current code do not do that because on the change tab build is not called.

Thanks to all!!

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

import '../models/task.dart';
import '../widgets/tasks_list_mob.dart';
import '../widgets/new_task.dart';

class TaskListName {
  final String shortName;
  final String longName;
  TaskListName(this.shortName, this.longName);
}

class TaksScreen extends StatefulWidget {
  @override
  _TaksScreenState createState() => _TaksScreenState();
}

class _TaksScreenState extends State<TaksScreen> with TickerProviderStateMixin {
  TabController _tabController;
  final Map<int, TaskListName> tasksListLabels = {
    0: TaskListName('Backlog', 'Tasks in backlog'),
    1: TaskListName('Tomorrow', 'Planed tasks for tomorrow'),
    2: TaskListName('Today', 'Your tasks for today'),
    3: TaskListName('Last work day', 'Tasks completed last working day'),
    4: TaskListName('Completed', 'Completed Tasks'),
    5: TaskListName('Backlog', 'Archived not completed tasks'),
  };

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 6, vsync: this, initialIndex: 2);
  }

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

  Future<void> showAddTaskDialog(BuildContext context) async {
    print(_tabController);
    await showDialog(
      context: context,
      builder: (context) => SimpleDialog(
        children: [NewTask(TaksBucket.backlog)],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final tasks = Provider.of<List<Task>>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(tasksListLabels[_tabController.index].longName),
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          tabs: [
            Tab(text: tasksListLabels[0].shortName),
            Tab(text: tasksListLabels[1].shortName),
            Tab(text: tasksListLabels[2].shortName),
            Tab(text: tasksListLabels[3].shortName),
            Tab(text: tasksListLabels[4].shortName),
            Tab(text: tasksListLabels[5].shortName),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          TasksListMob(tasks, TaksBucket.backlog),
          TasksListMob(tasks, TaksBucket.tomorrow),
          TasksListMob(tasks, TaksBucket.today),
          TasksListMob(tasks, TaksBucket.completed),
          TasksListMob(tasks, TaksBucket.completed),
          TasksListMob(tasks, TaksBucket.archived),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () => showAddTaskDialog(context),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
    );
  }
}

After @mkobuolys solution implementation - the scroll handle is still missing.


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

import '../models/task.dart';
import '../widgets/tasks_list_mob.dart';
import '../widgets/new_task.dart';

class TaskListName {
  final String shortName;
  final String longName;
  TaskListName(this.shortName, this.longName);
}

class TaksScreen extends StatefulWidget {
  @override
  _TaksScreenState createState() => _TaksScreenState();
}

class _TaksScreenState extends State<TaksScreen> with TickerProviderStateMixin {
  TabController _tabController;
  var _tabIndex = ValueNotifier(2);
  final Map<int, TaskListName> tasksListLabels = {
    0: TaskListName('Backlog', 'Tasks in backlog'),
    1: TaskListName('Tomorrow', 'Planed tasks for tomorrow'),
    2: TaskListName('Today', 'Your tasks for today'),
    3: TaskListName('Last work day', 'Tasks completed last working day'),
    4: TaskListName('Completed', 'Completed Tasks'),
    5: TaskListName('Backlog', 'Archived not completed tasks'),
  };

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 6, vsync: this, initialIndex: 2);
  }

  Future<void> showAddTaskDialog(BuildContext context) async {
    await showDialog(
      context: context,
      builder: (context) => SimpleDialog(
        children: [NewTask(TaksBucket.backlog)],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final tasks = Provider.of<List<Task>>(context);
    return Scaffold(
      appBar: AppBar(
        title: **ValueListenableBuilder(
          valueListenable: _tabIndex,
          builder: (context, value, child) =>
              Text(tasksListLabels[value].longName),
        ),**
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          onTap: (value) {
            _tabIndex.value = _tabController.index;
          },
          tabs: [
            Tab(text: tasksListLabels[0].shortName),
            Tab(text: tasksListLabels[1].shortName),
            Tab(text: tasksListLabels[2].shortName),
            Tab(text: tasksListLabels[3].shortName),
            Tab(text: tasksListLabels[4].shortName),
            Tab(text: tasksListLabels[5].shortName),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          TasksListMob(tasks, TaksBucket.backlog),
          TasksListMob(tasks, TaksBucket.tomorrow),
          TasksListMob(tasks, TaksBucket.today),
          TasksListMob(tasks, TaksBucket.completed),
          TasksListMob(tasks, TaksBucket.completed),
          TasksListMob(tasks, TaksBucket.archived),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () => showAddTaskDialog(context),
      ),
    );
  }
}

At the end it was really easy to make it done!

  • I did create ValueNotifier _tabIndex
  • At init state I addListener on TabControler. Listener set value for _tabIndex(ValueNotifier). I did dispose Listener in dispose...
  • In the build method add ValueListenableBuilder to the title.
  • Final code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../models/task.dart';
import '../widgets/tasks_list_mob.dart';
import './tasks_notifications_screen.dart';
import '../widgets/new_task.dart';
import '../widgets/count_batch_icon_button.dart';
import '../consts/consts.dart';
import '../util/tasks_helper.dart';

class TaskListName {
  final String shortName;
  final String longName;
  final TaksBucket bucket;
  TaskListName(this.shortName, this.longName, this.bucket);
}

class TaksScreen extends StatefulWidget {
  @override
  _TaksScreenState createState() => _TaksScreenState();
}

class _TaksScreenState extends State<TaksScreen> with TickerProviderStateMixin {
  TabController _tabController;
  var _tabIndex = ValueNotifier(2);

  final Map<int, TaskListName> tasksListLabels = {
    0: TaskListName('Backlog', 'Tasks in backlog', TaksBucket.backlog),
    1: TaskListName(
        'Tomorrow', 'Planed tasks for tomorrow', TaksBucket.tomorrow),
    2: TaskListName('Today', 'Your tasks for today', TaksBucket.today),
    3: TaskListName('Last work day', 'Tasks completed last working day',
        TaksBucket.completed),
    4: TaskListName('Completed', 'Completed Tasks', TaksBucket.completed),
    5: TaskListName(
        'Archived', 'Archived not completed tasks', TaksBucket.archived),
  };

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 6, vsync: this, initialIndex: 2);
    _tabController.addListener(() {
      _tabIndex.value = _tabController.index;
    });
  }

  @override
  void dispose() {
    super.dispose();
    _tabController.removeListener(() {
      _tabIndex.value = _tabController.index;
    });
  }

  Future<void> showAddTaskDialog(BuildContext context) async {
    await showDialog(
      context: context,
      builder: (context) => SimpleDialog(
        children: [NewTask(tasksListLabels[_tabController.index].bucket)],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final tasks = Provider.of<List<Task>>(context);
    return Scaffold(
      appBar: AppBar(
        title: ValueListenableBuilder(
          valueListenable: _tabIndex,
          builder: (context, value, child) =>
              Text(tasksListLabels[value].longName),
        ),
        actions: [
          CountBatchIconButton(
            TasksHelper.numberOfTasksForNotification(tasks),
            Icon(Icons.notifications_none),
            () => Navigator.of(context).pushNamed(TasksNotifications.routeName),
          ),
        ],
        bottom: TabBar(
          controller: _tabController,
          isScrollable: true,
          indicatorColor: TaskConsts.mainColor,
          tabs: [
            Tab(text: tasksListLabels[0].shortName),
            Tab(text: tasksListLabels[1].shortName),
            Tab(text: tasksListLabels[2].shortName),
            Tab(text: tasksListLabels[3].shortName),
            Tab(text: tasksListLabels[4].shortName),
            Tab(text: tasksListLabels[5].shortName),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          TasksListMob(tasks, TaksBucket.backlog),
          TasksListMob(tasks, TaksBucket.tomorrow),
          TasksListMob(tasks, TaksBucket.today),
          TasksListMob(
            tasks,
            TaksBucket.completed,
            isLastWorkingDay: true,
          ),
          TasksListMob(tasks, TaksBucket.completed),
          TasksListMob(tasks, TaksBucket.archived),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: TaskConsts.mainColor,
        child: Icon(Icons.add),
        onPressed: () => showAddTaskDialog(context),
      ),
    );
  }
}

Solution

  • Inside the TabBar widget, add onTap callback with the setState method to trigger rebuild, hence updating the name:

    ...
    appBar: AppBar(
      title: Text(tasksListLabels[_tabController.index].longName),
      bottom: TabBar(
        controller: _tabController,
        isScrollable: true,
        tabs: [
          Tab(text: tasksListLabels[0].shortName),
          Tab(text: tasksListLabels[1].shortName),
          Tab(text: tasksListLabels[2].shortName),
          Tab(text: tasksListLabels[3].shortName),
          Tab(text: tasksListLabels[4].shortName),
          Tab(text: tasksListLabels[5].shortName),
        ],
        onTap: (_) {
          setState((){});
        },
      ),
    ),
    ...