XXXController not found you need to call Get.put or Get.lazyPut

Issue

I am trying to create simple app with next layout:

enter image description here

Buttons should switch Widget1\Widget2.

I wrote next code (copy-past ready):

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:get/get.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialBinding: HomeBinding(),
      initialRoute: '/widget1',
      home: Scaffold(
          appBar: AppBar(
            title: Text("Demo"),
          ),
          body: SafeArea(
            child: IndexedStack(
              index: Get.find<NavigationBarController>().tabIndex.value,
              children: [
                Widget1(), 
                Widget2()
              ],
            ),
          ),
          bottomNavigationBar: BottomNavigationBar(
              currentIndex: Get.find<NavigationBarController>().tabIndex.value,
              onTap: Get.find<NavigationBarController>().changeTabIndex,
              items: [
                _bottomNavigationBarItem(
                  icon: CupertinoIcons.home,
                  label: 'Button1',
                ),
                _bottomNavigationBarItem(
                  icon: CupertinoIcons.rocket_fill,
                  label: 'Button2',
                ),
              ])),

      getPages: [
        GetPage(
          name: '/widget1',
          page: () => Widget1(),
          binding: Widget1_Bindings(),
        ),
        GetPage(
          name: '/widget2',
          page: () => Widget2(),
          binding: Widget2_Bindings(),
        ),
      ],
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.system,
    );
  }
}

class NavigationBarController extends GetxController {
  static NavigationBarController get to => Get.find();
  var tabIndex = 0.obs;

  void changeTabIndex(int index) {
    tabIndex.value = index;
    update();
  }

  @override
  void onInit() {
    super.onInit();
  }

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

class Controller1 extends GetxController {
  var _test2 = "test1".obs;
  get test1 => this._test2.value;
}

class Controller2 extends GetxController {
  var _test2 = "test2".obs;
  get test1 => this._test2.value;
}

class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("Widget1"),
    );
  }
}


class HomeBinding extends Bindings {
  @override
  void dependencies() {
     Get.lazyPut(() => NavigationBarController());
  }
  
}

class Widget1_Bindings extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => Controller1());
  }
}

class Widget2_Bindings extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => Controller2());
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Text("Widget2"),
    );
  }
}

_bottomNavigationBarItem({required IconData icon, required String label}) {
  return BottomNavigationBarItem(
    icon: Icon(icon),
    label: label,
  );
}

Code have two problems. First one is that I am getting error:
enter image description here

The second one is that I can;t figure out how to get GetX routing like: Get.to work.

Solution

You have a few issues here.

First off, you have an initialRoute and a home defined in your GetMaterialApp and they’re 2 different destinations. Use one or the other, not both.

You were binding the NavigationBarController to what was defined in your initialRoute but the app was trying to go to the Scaffold you have in the home property. So that binding was never getting called and that is the reason for the controller not found error.

Since your NavigationBarController is basically needed globally, and not on a page by page basis, I’d lose the binding for that and just initialize it in main before running the app.

void main() {
  Get.put(NavigationBarController());
  runApp(MyApp());
}

Second, if you’re using an observable variable from a GetX class, and you’re expecting a change in the UI, it needs to be wrapped in an Obx or GetX widget. So wrap your BottomNavigationBar in an Obx.

   bottomNavigationBar: Obx(
        () => BottomNavigationBar(
          currentIndex: Get.find<NavigationBarController>().tabIndex.value,
          onTap: Get.find<NavigationBarController>().changeTabIndex,
          items: [
            _bottomNavigationBarItem(
              icon: CupertinoIcons.home,
              label: 'Button1',
            ),
            _bottomNavigationBarItem(
              icon: CupertinoIcons.rocket_fill,
              label: 'Button2',
            ),
          ],
        ),
      ),

Same thing with your IndexedStack. It needs to be wrapped in an Obx to rebuild on changes of the tabIndex.

Obx(
    () => IndexedStack(
    index: Get.find<NavigationBarController>().tabIndex.value,
    children: [Widget1(), Widget2()],
   ),
  ),

Now your BottomNavigationBar will work. As for bindings, since your app is based on tabs and not navigating to full pages, you’re never having to manually navigate to a page with Get.to(() => Widget1());, That scenario is when Bindings can come in handy, but for this case I don’t see any benefit to using them. I would just initialize the controller in each tab. So overall it would look like this. I got rid of the getPages also because they’re not getting used.

void main() {
  Get.put(NavigationBarController());
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: HomePage(),
      debugShowCheckedModeBanner: false,
      themeMode: ThemeMode.system,
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: SafeArea(
        child: Obx(
          () => IndexedStack(
            index: Get.find<NavigationBarController>().tabIndex.value,
            children: [Widget1(), Widget2()],
          ),
        ),
      ),
      bottomNavigationBar: Obx(
        () => BottomNavigationBar(
          currentIndex: Get.find<NavigationBarController>().tabIndex.value,
          onTap: Get.find<NavigationBarController>().changeTabIndex,
          items: [
            _bottomNavigationBarItem(
              icon: CupertinoIcons.home,
              label: 'Button1',
            ),
            _bottomNavigationBarItem(
              icon: CupertinoIcons.rocket_fill,
              label: 'Button2',
            ),
          ],
        ),
      ),
    );
  }
}

class NavigationBarController extends GetxController {
  static NavigationBarController get to => Get.find();
  var tabIndex = 0.obs;

  final widgetList = <Widget>[Widget1(), Widget2()];

  void changeTabIndex(int index) {
    tabIndex.value = index;
  //  update(); don't need this, only if you're using GetBuilder
  }

  @override
  void onInit() {
    super.onInit();
  }

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

class Controller1 extends GetxController {
  var _test2 = "test1".obs;
  get test1 => this._test2.value;
}

class Controller2 extends GetxController {
  var _test2 = "test2".obs;
  get test1 => this._test2.value;
}

class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = Get.put(Controller1());
    return Center(
      child: Container(
        child: Obx(() => Text(controller.test1)),
      ),
    );
  }
}

class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = Get.put(Controller2());
    return Center(
      child: Container(
        child: Obx(() => Text(controller.test1)),
      ),
    );
  }
}

_bottomNavigationBarItem({required IconData icon, required String label}) {
  return BottomNavigationBarItem(
    icon: Icon(icon),
    label: label,
  );
}

Also if it were me I’d be using GetBuilder for everything and not observable streams, because you’re not doing anything that really justifies a stream. They both work, but GetBuilder is the most performant option.

UPDATE:

To answer your question in the comment: No, you don’t need an IndexedStack.

You could pass in a list of widgets at a given index and when the index changes the Obx rebuilds. The note about const constructors is not related to this but something you should do whenever you can.

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);
  final _widgetList = const <Widget>[
    Widget1(),
    Widget2()
  ]; // add const constructors to both these widgets

  @override
  Widget build(BuildContext context) {
    final controller = Get.find<NavigationBarController>();
    return Scaffold(
      appBar: AppBar(
        title: Text("Demo"),
      ),
      body: SafeArea(
        child: Obx(
          () => Center(
            child: _widgetList[controller.tabIndex.value], // or _widgetList.elementAt(controller.tabIndex.value)
          ),
        ),
      ),
...

Answered By – Loren.A

Answer Checked By – Gilberto Lyons (FlutterFixes Admin)

Leave a Reply

Your email address will not be published.