Flutter change main appbar title on other pages - dart

Looking for some assistance with changing the AppBar title on subsequent pages, so me being tabs and some not. MyApp is defined on the authentication page of my app. I then goto a new page that holds the tabs, then I have other pages off some of the tab pages, what I want to be able to do is, instead of putting another AppBar under the main one, I just want to change the title of the main AppBar when I am on any of the other pages.
Any ideas how to do this, I saw 1 example that did not fit because my tabs are setup different and could not make it fit, thought maybe there was a way to define the title initially so that I can change state or something and change the title.
Any ideas or thoughts on this?

You can add a TabController and add listen to it such that you call setState whenever you are switching between the Tabs, and change the AppBar title accordingly.
import "package:flutter/material.dart";
void main(){
runApp(new MaterialApp(home:new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin{
final List<MyTabs> _tabs = [new MyTabs(title: "Teal",color: Colors.teal[200]),
new MyTabs(title: "Orange",color: Colors.orange[200])
];
MyTabs _myHandler ;
TabController _controller ;
void initState() {
super.initState();
_controller = new TabController(length: 2, vsync: this);
_myHandler = _tabs[0];
_controller.addListener(_handleSelected);
}
void _handleSelected() {
setState(() {
_myHandler= _tabs[_controller.index];
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text(_myHandler.title),
backgroundColor: _myHandler.color,
bottom: new TabBar(
controller: _controller,
tabs: <Tab>[
new Tab(text: _tabs[0].title,),
new Tab(text: _tabs[1].title,)
],
),),
);
}
}
class MyTabs {
final String title;
final Color color;
MyTabs({this.title,this.color});
}

With the little help of above answer, I could write a simple and beginner friendly code.
The concept is easy, get current tab index and change title with set state. You need to "detect" when the active tab is changed. That is why we add listener (the one who detects CHANGE, AND ACTS)
Let me know in comments if below code doesn't make sense
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController _tcontroller;
final List<String> titleList = ["Home Page", "List Page", "Message Page"];
String currentTitle;
#override
void initState() {
currentTitle = titleList[0];
_tcontroller = TabController(length: 3, vsync: this);
_tcontroller.addListener(changeTitle); // Registering listener
super.initState();
}
// This function is called, every time active tab is changed
void changeTitle() {
setState(() {
// get index of active tab & change current appbar title
currentTitle = titleList[_tcontroller.index];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(currentTitle),
centerTitle: true,
bottom: TabBar(
controller: _tcontroller,
tabs: <Widget>[
Icon(Icons.home),
Icon(Icons.format_list_bulleted),
Icon(Icons.message),
],
),
),
body: TabBarView(
controller: _tcontroller,
children: <Widget>[
Center(child: Text(titleList[0])),
Center(child: Text(titleList[1])),
Center(child: Text(titleList[2])),
],
),
);
}
}

Related

Flutter: how to access ScopeModel properties in child pages

I am trying to understand ScopeModel in Flutter and need some help on how access values from the model on a different page
My home page has a bottom navigation bar and when click just display the search page. I have wrap the widget tree with the ScopeModel and added the model.
The count is getting incremented but I am not sure how to access it from the search page
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
ScopeCounter sc;
#override
void initState() {
super.initState();
sc = new ScopeCounter();
}
final List<Widget> _children = [
..
Search()
];
var _currentIndex = 0;
#override
Widget build(BuildContext context) {
return ScopedModel(
model:sc ,
child: Scaffold(
appBar: AppBar(
title: Center(child: Text("test")),
),
drawer: JobsDrawer(),
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: onTabTapped, // new
currentIndex: _currentIndex, // new
items: [
new BottomNavigationBarItem(
icon: new Icon(Icons.search),
title: new Text("search"),
)
])),
);
}
void onTabTapped(int index) {
setState(() {
sc.increment();
print(sc.counter1.count);
_currentIndex = index;
});
}
}
This my Model
class ScopeCounter extends Model {
Counter counter1 = Counter();
increment() {
counter1.count += 1;
}
}
class Counter {
int count = 1;
}
Search page
class Search extends StatefulWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
...
}
}
I would like access the count from the "search" page.
Thanks for your help
You just have to wrap your SearchPage's Widget (Scaffold in this case) with a ScopedModelDescendant Widget. This gives you access to your ScopedModel.
A great explanation can be found in the documentation: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
EDIT: Also your ScopedModel must be a parent of both: MyHomePage and Search.

How to listen to Drawer open/close animation in Flutter

Being new to Flutter, I'm doing a learning exercise by re-creating my existing Android app. However I'm having trouble to produce a 'spinning, growing home icon', which should be animated in sync with the drawer open/close animation.
The desired drawer/home-icon behaviour looks like this:
I made this in Android by implementing
DrawerListener.onDrawerSlide(View drawerView, float slideOffset)
My naive approach to do this in Flutter, is to use a ScaleTransition and a RotationTransition that listen to the same Animation that opens/closes the Drawer.
I can see that ScaffoldState has a DrawerControllerState, but it is private.
final GlobalKey<DrawerControllerState> _drawerKey = new GlobalKey<DrawerControllerState>();
And even if I could somehow access the DrawerControllerState (which I don't know how), I then couldn't access _animationChanged() and _controller because both are private members of DrawerControllerState.
I feel that I'm coming at this in the wrong way, and that there is an better approach that's more natural to Flutter, that I'm unable to see.
Please can anyone describe the Flutter way of implementing this?
You can first refer to other people's replies on stackoverflow here
My solve:
get Drawer status on DrawerWidget
initState() : open drawer
dispose() : close drawer
Stream drawer status by DrawerService Provider
see full code
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:provider/provider.dart';
void main() {
runApp(
MultiProvider(
providers: [
Provider(create: (_) => DrawerService()),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DrawerService _drawerService;
String drawerStatus = 'close';
#override
void initState() {
super.initState();
_drawerService = Provider.of(context, listen: false);
_listenDrawerService();
}
_listenDrawerService() {
_drawerService.status.listen((status) {
if(status) {
drawerStatus = 'open';
} else {
drawerStatus = 'close';
}
setState(() { });
});
}
#override
Widget build(BuildContext context) {
Color bgColor = Colors.yellow;
if(drawerStatus == 'open') {
bgColor = Colors.red;
}
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: DrawerWidget(),
body: Container(
decoration: BoxDecoration(color: bgColor),
height: 300,
child: Center(child: Text(drawerStatus),),
),
);
}
}
class DrawerWidget extends StatefulWidget {
#override
_DrawerWidgetState createState() => _DrawerWidgetState();
}
class _DrawerWidgetState extends State<DrawerWidget> {
DrawerService _drawerService;
#override
void initState() {
super.initState();
_drawerService = Provider.of(context, listen: false);
_drawerService.setIsOpenStatus(true);
}
#override
Widget build(BuildContext context) {
return Drawer(
child: Center(child: Text('drawer'),),
);
}
#override
void dispose() {
super.dispose();
_drawerService.setIsOpenStatus(false);
}
}
class DrawerService {
StreamController<bool> _statusController = StreamController.broadcast();
Stream<bool> get status => _statusController.stream;
setIsOpenStatus(bool openStatus) {
_statusController.add(openStatus);
}
}
hope to help some body

Flutter: Textfield in overlay triggering rebuilds

I can't seem to figure out why the code below prints built three times (calls State.build) after you hit the button to show the Overlay and focus the Textfield.
Now, I know that a MaterialApp inside another MaterialApp is not a good idea and that's the second part of the problem: Why won't the Keyboard (testing on a physical device with Android 8.1.0) appear when I remove the MaterialApp wrapped around the Scaffold and try to focus the Textfield? There is a MaterialApp at the root whose Overlay Overlay.of(context) should find.
import "package:flutter/material.dart";
import "package:flutter/services.dart";
void main() {
SystemChrome.setEnabledSystemUIOverlays([]);
runApp(
MaterialApp(
home: Scaffold(
body: MyOtherApp()
)
)
);
}
class MyAppState extends State<MyApp> {
TextEditingController controller = TextEditingController();
#override
Widget build(BuildContext context) {
controller.text = "placeholder";
return MaterialApp(
home: Scaffold(
body: (){
print("built");
return TextField(
controller: controller,
);
}()
)
);
}
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyOtherApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: FlatButton(
child: Text(
"show overlay",
),
onPressed: () {
Overlay.of(context).insert(
OverlayEntry(
builder: (context) {
return MyApp();
}
)
);
}
)
);
}
}
In my case I put SystemChrome.setEnabledSystemUIOverlays([]); under the build instead in the main.

Flutter Listview in stateless widget with initial offset

I have my own StatelessWidget with a ListView. I want it's state to be managed by parent StatefulWidget.
The behaviour I desire is that if I change a value, listView scrolls (or even jumps - it doesn't matter) to that value.
I thought that if I create stateless widget every time parent's setState() method is being invoked, the scrollController with initialOffset would make the list "move" but it doesn't. What is worth mentioning is that on first build initialOffset works as it should.
Here is example code of my problem:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 5;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new MyClass(_counter),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
child: new Icon(Icons.add),
),
);
}
}
class MyClass extends StatelessWidget {
final int extraValue;
final ScrollController scrollController;
MyClass(this.extraValue):
scrollController = new ScrollController(initialScrollOffset: extraValue*50.0);
#override
Widget build(BuildContext context) {
return new ListView.builder(
itemExtent: 50.0,
itemCount: 100,
controller: scrollController,
itemBuilder: (BuildContext context, int index) {
if (index != extraValue)
return new Text(index.toString());
else
return new Text("EXTRA" + index.toString());
});
}
}
I'm not sure if it's a bug or my mistake.
Any ideas might be helpful :)
EDIT:
Inspired by Ian Hickson's answer I have solution to my problem:
void _incrementCounter() {
setState(() {
_counter++;
myClass.scrollController.animateTo(_counter*50.0, duration: new Duration(seconds: 1), curve: new ElasticOutCurve());
});
}
The initial offset is... the initial offset. Not the current offset. :-)
You can cause the offset to change by calling methods on the ScrollController, like animateTo.

ExpansionTile doesn't keep state

following problem:
I have a list of ExpansionTiles which works very well. The only problem I'm facing is that a expanded ExpansionTile which is scrolled out of view will, after scrolling it into view again, no longer be expanded. This leads to undesired user experience and also a kind of "jumpy" scrolling.
The documentation states the following:
When used with scrolling widgets like ListView, a unique key must be specified to enable the ExpansionTile to save and restore its expanded state when it is scrolled in and out of view.
This doesn't work though. So far I have found no way to make this work.
Here is the code so far:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'ExpansionTile Test',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _getChildren() {
List<Widget> elements = [];
for (int i = 0; i < 20; i++) {
elements.add(new ListChild());
}
return elements;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ExpansionTile Test'),
),
body: new ListView(
children: _getChildren(),
),
);
}
}
class ListChild extends StatefulWidget {
#override
State createState() => new ListChildState();
}
class ListChildState extends State<ListChild> {
GlobalKey<ListChildState> _key = new GlobalKey();
#override
Widget build(BuildContext context) {
return new ExpansionTile(
key: _key,
title: const Text('Test Tile'),
children: <Widget>[
const Text('body'),
],
);
}
}
Use a PageStorageKey instead of a GlobalKey.

Resources