Unexpected behavior of AppBar when extended from another class

50 views Asked by At

In my first Flutter app I want to build a responsive view. For state handling I am using the stacked framework. Part of the responsive view will be an AppBar widget which will display (or not) a hamburger menu based on the available screen width. I created a class for the responsive AppBar.

import 'package:flutter/material.dart';
import 'package:chd_stacked/ui/common/ui_helpers.dart';

class AppBarResponsive extends AppBar {
  AppBarResponsive({Key? key, this.breakpoint = 600,}) : super(key: key);

  final double breakpoint;

  @override
  Widget build(BuildContext context) {
    return AppBar(
      automaticallyImplyLeading: false,
      leading: screenWidth(context) <= breakpoint ? 
        const IconButton(icon: Icon(Icons.menu), onPressed: null) : null);
  }
}

The screenWidth function is defined in the ui_helpers.dart which is part of the stacked framework:

double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;

Based on what I have read so far, the fact that MediaQuery.of(context).size.width is called inside the build override causes the build function to get called again every time the size changes. At least that's how I understand it.

In the responsive view I am using the AppBarResponsive like below:

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:chd_stacked/ui/common/app_colors.dart';
import 'package:chd_stacked/ui/common/ui_helpers.dart';
import 'home_viewmodel.dart';
import 'package:chd_stacked/ui/widgets/common/app_bar_responsive/app_bar_responsive.dart';

class HomeView extends StackedView<HomeViewModel> {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget builder(
    BuildContext context,
    HomeViewModel viewModel,
    Widget? child,
  ) {
    return Scaffold(
      appBar: AppBarResponsive(breakpoint: 600,),
      body: ... removed for clarity ...
    );
  }

  @override
  HomeViewModel viewModelBuilder(
    BuildContext context,
  ) =>
      HomeViewModel();
}

When I run the app the layout is as supposed to be but the menu icon doesn't show up at all regardless of the screen width.

However, when I use an AppBar (instead of an AppBarResponsive) directly in the responsive view:

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import 'package:chd_stacked/ui/common/app_colors.dart';
import 'package:chd_stacked/ui/common/ui_helpers.dart';
import 'home_viewmodel.dart';

class HomeView extends StackedView<HomeViewModel> {
  const HomeView({Key? key}) : super(key: key);

  @override
  Widget builder(
    BuildContext context,
    HomeViewModel viewModel,
    Widget? child,
  ) {
    return Scaffold(
      appBar: AppBar(
          automaticallyImplyLeading: false,
          leading: screenWidth(context) <= 600
              ? const IconButton(icon: Icon(Icons.menu), onPressed: null)
              : null),
      body: ... removed for clarity ...
    );
  }

  @override
  HomeViewModel viewModelBuilder(
    BuildContext context,
  ) =>
      HomeViewModel();
}

The menu icon appears and disappears based on the screen width as expected.

The question is why it doesn't work as a class that extends AppBar but it works when the AppBar widget is used directly into the view? Am I missing something here?

1

There are 1 answers

2
cyberail On BEST ANSWER

Problem is not the MediqQouery, though everything that was said about Mediquery is true.

Problem is how you are creating your custom appear, you can't extend AppBar and override the build method, one of the first reason is that AppBar is stateful and AppBar itself does not have any build method it is implemented in its state widget.

Secondly proper way to make custom appear from AppBar is this(Though I prefer to use PreferedSizeWidget when making custom appbars):

import 'package:flutter/material.dart';

double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;

class AppBarResponsive extends AppBar {
  AppBarResponsive({
    Key? key,
    required BuildContext context,
    this.breakpoint = 600,
  }) : super(
          automaticallyImplyLeading: false,
          leading: ...
          title: Text('.....')
        );

  final double breakpoint;
}

Thirdly, you will need to access context inside the super() because you need to call screenWidth there which depends on context to calculate width, for that you need to pass down the context argument from AppBarResponsive's constructor, like so:

class AppBarResponsive extends AppBar {
  AppBarResponsive({
    Key? key,
    required BuildContext context,
    this.breakpoint = 600,
  }) : super(
          automaticallyImplyLeading: false,
          leading: screenWidth(context) <= breakpoint
              ? const IconButton(icon: Icon(Icons.menu), onPressed: null)
              : null,
        );

  final double breakpoint;
}

Note: Or you can pass already calculated breakpoint from home page and compare it inside the custom appear, this way you will avoid passing context in contractor(if it is weird for you).

Now you have context and you can calculate the width during the build, next is to just include it inside the home page like this and you are done:

import 'package:flutter/material.dart';
import 'package:test_app_1/my_app_bar.dart';

class HomeWidget extends StatelessWidget {
  const HomeWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBarResponsive(
        breakpoint: 600,
        context: context,
      ),
      body: ...,
    );
  }
}

Note: I removed StackedView for easy testing, but StackView does not make any difference here.