5

I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?

void main() => runApp(MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Flutter App",
      theme: ThemeData(
        primaryColor: Colors.cyan,
        brightness: Brightness.dark
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text("Main Dart"),
        ),
        body: HomeScreen(),
      ),
      routes: <String, WidgetBuilder>{
        '/homeScreen': (buildContext)=>HomeScreen(),
        '/second': (buildContext)=>Second() 
      },
    );
  }
}

//HomeScreen or Second Widget on different dart file

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //here I want to change the title of Main Dart to HomeScreen
    return Container(
      child: Center(
        child: FlatButton(
          child: new Text("Home screen"),
          onPressed: (){
            Route route = MaterialPageRoute(builder: (context) => Second());
            Navigator.push(context, route);
          },
        ),
      ),
    );
  }
}

or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?

flix
  • 1,688
  • 3
  • 34
  • 64
  • can you post `Second` class , are you using Scaffold in every route – Doc Apr 18 '19 at 04:05
  • @Doc i want to avoid `Scaffold` on every route, so my `second` route same as `HomeScreen` – flix Apr 18 '19 at 04:06
  • is it completely unavoidable to have scafffolds ? – Doc Apr 18 '19 at 04:08
  • 1
    @doc the main question in my mind is __"why i need to put `Scaffold` on every page just to change the title of `appBar`?"__ | it's just my question as a beginner flutter dev, and any suggest are welcome – flix Apr 18 '19 at 04:12
  • seems like a legit doubt; looking into it. – Doc Apr 18 '19 at 04:17
  • i think i found a way – Doc Apr 18 '19 at 04:20
  • would wait to see it soon @Doc – flix Apr 18 '19 at 04:22
  • I found that currently there is no way to have an AppBar without Scaffold. Can't find the link to the post but there is an issue on github. – Doc Apr 18 '19 at 04:49
  • also, this looks similar to what you want to do https://stackoverflow.com/questions/53957287/is-it-possible-to-pass-appbar-from-body-in-scaffold-widget – Doc Apr 18 '19 at 04:58
  • Seems hard to do this, I think i need to defined scaffold for each screen, thanks anyway – flix Apr 18 '19 at 05:00
  • does the appbar gets any other change except for the title? I mean does separate route have same appbar or different appbar? – Doc Apr 18 '19 at 05:06
  • I have posted my sample code based on the idea that you want to change only the title in each page and not the working of Scaffold. The latter will require separate Scaffold in each page. – Doc Apr 18 '19 at 05:54

5 Answers5

6

Have a BLoC for app properties in app_properties_bloc.dart

final appBloc = AppPropertiesBloc();

class AppPropertiesBloc{
  StreamController<String> _title = StreamController<String>();

  Stream<String> get titleStream => _title.stream;

  updateTitle(String newTitle){
    _title.sink.add(newTitle);
  }

  dispose() {
    _title.close();
  }
}

Use stream builder in AppBar like this:

AppBar(
   title: StreamBuilder<Object>(
       stream: appBloc.titleStream,
       initialData: "Main Dart",
       builder: (context, snapshot) {
           return Text(snapshot.data);
       }
   ),
),

Use this to update title on button's onPressed()

onPressed: () {
    appBloc.updateTitle('new title');
},
Kalpesh Kundanani
  • 5,413
  • 4
  • 22
  • 32
  • 11
    why does it look like killing a fly with a machine gun?? – Doc Apr 18 '19 at 04:42
  • 1
    too much for a simple task. and I thought Flutter will reduce code :( – Doc Apr 18 '19 at 04:45
  • This is because you just have one property that changes like this and for that one property you don't feel like it's worth it.. but suppose you have multiple properties like this then you won't feel the same.. Using StreamBuilder to update the data is efficient, as it will build only the widget that needs an update.. – Kalpesh Kundanani Apr 18 '19 at 05:04
  • yes i thought the same thing, it is nice if there are a bunch of things but looks an overkill for just one thing. – Doc Apr 18 '19 at 05:07
  • @KalpeshKundanani will it work in case of `Navigator.of(context).pop();`? – Hemant Kaushik Apr 18 '19 at 05:28
  • @HemantKaushik for this to work you just need to call 'appBloc.updateTitle('new title');'. So while navigating if you can call this method then it will work. – Kalpesh Kundanani Apr 18 '19 at 07:27
2

Just in case you are changing only the title of Scaffold then this will work.

I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
      'home': (context) => SOMain(),
      '/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
      '/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
    });
  }
}

class DefaultScaffold extends StatelessWidget {
  String title;
  Widget body;

  DefaultScaffold(this.title, this.body);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: body,
    );
  }
}

class SOMain extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultScaffold(
      "Main Screen",
      Center(
        child: RaisedButton(
            child: Text("Go to second screen"),
            onPressed: () {
              Navigator.pushNamed(context, '/secondPage');
            }),
      ),
    );
  }
}

class SOSecond extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        child: Text("Go the 3rd screen"),
        onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
      ),
    );
  }
}

class SOThird extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(child: Text("You are on last screen"));
  }
}

Note: This is a simple workaround and may not be the best way to do this.

Doc
  • 10,831
  • 3
  • 39
  • 63
0

You can accomplish updating the state of the parent from a child by using a callback function.

Parent Class:

import 'package:flutter/material.dart';

class Parent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ParentState();
  }
}

class ParentState extends State<Parent> {

  String title = "Old Title";

  @override
  Widget build(BuildContext context) {

    return new Scaffold(
        appBar: new AppBar(
        title: new Text(title),
      ),
      body: DaysFragmentView(onTitleSelect: (String value) {
            setTitle(value);
        }
      ),
    );

  }

  void setTitle(String value) {
    setState(() {
      title = value;
    });
  }
}

Child Class


typedef TitleCallback = void Function(Title color);

class DaysFragmentView extends StatelessWidget {
  const DaysFragmentView({this.onTitleSelect});

  final TitleCallback onTitleSelect;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        RaisedButton(
          child: Text('One'),
          onPressed: () {
            onTitleSelect("TITLE ONE");
          },
        ),
        RaisedButton(
          child: Text('Two'),
          onPressed: () {
            onTitleSelect("TITLE TWO");
          },
        )
      ],
    );
  }
}

Reference:

0

Using ValueListenableBuilder is an option.

Use an instance variable

String appTitle;

Then set the app bar as in the following block:

appBar: AppBar(
          ValueListenableBuilder<String>(
          valueListenable: appTitle,
          builder: (context, value, child) {
            return Text(appTitle.value);
          },
        ),

After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.

appTitle.value = "Home Screen";
0

Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.

You can run the example in dart pad

import 'package:flutter/material.dart';

void main() {
  runApp(const MyHomePage(title: 'init title'));
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: ValueListenableBuilder<String?>(
            builder: (BuildContext context, String? value, Widget? child) {
              return Text(value ?? widget.title);
            },
            valueListenable: _appBarTitleNotifier,
          ),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
            ],
          ),
        ),
      ),
    );
  }
}

class ChildDemoTitleBar extends StatefulWidget {
  final ValueNotifier<String?> titleNotifier;

  const ChildDemoTitleBar({Key? key, required this.titleNotifier})
      : super(key: key);

  @override
  State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}

class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Padding(
        padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
        child: InkWell(
            onTap: () {
              _counter++;
              widget.titleNotifier.value = "title updated $_counter";
            },
            child: const Text("tap to update title")));
  }
}
Zhar
  • 3,330
  • 2
  • 24
  • 25