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?
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.