Navigator Example Part 1.5

In the last navigator example over at https://whitewhiskywolf.com/flutter-navigator-v2-part-1/ we did a basic app. I sat down to record the video for that, and discovered a bug. When you go to the cart through a product, you can't go back to the home screen and then the cart. It just doesn't work. I figured this would be a simple fix, and well 2 hours later it was and wasn't. If you didn't read the last post, take a look at the link! If you just want to see the github checkout https://github.com/cadaniel/FlutterNavigator2Example

 

Forgotten State

So there's a home screen state. I didn't add a way to go back to products. So let's add it.

@injectable
class HomeCubit extends Cubit<HomeState> {
  HomeCubit() : super(HomeState.products());

  void navigateToCart() => emit(HomeState.cart());

  void navigateToProduct(Product product) => emit(HomeState.product(product));

  void navigateToProducts() => emit(HomeState.products());
}

Easy enough here, we add navigatoToProducts to emit the products state! Now we can sync the router and the state.

The fundemental problem

So after looking at what I was doing, the fundamental problem is I was trying to store state in two places. State only belongs in one, and this was a reminder to me as why. Let's look at our onPopPage method.

Adjusting the HomeState Cubit

So we're going to move all state to the cubit, we need to actually hold it there. Our current state is already stored, so let's add the previous state.

  HomeState previousState = HomeState.products();

Next up, we need to store the previous state when we chagne it. So our functions will now look like


  void navigateToCart() {
    previousState = state;
    emit(HomeState.cart());
  }

  void navigateToProduct(Product product) {
    previousState = state;
    emit(HomeState.product(product));
  }

  void navigateToProducts() {
    previousState = state;
    emit(HomeState.products());
  }

With that sorted let's look at our HomeRouteDelegate.

Delegate Changes

The first thing we'll look at is the onPopPage method.

        onPopPage: (route, result) {
          if (!route.didPop(result) || cubit.state == HomeState.products()) {
            return false;
          }
          cubit.state.when(
            products: () {
              cubit.navigateToProducts();
            },
            product: (product) {
              cubit.navigateToProducts();
            },
            cart: () {
              cubit.previousState.when(
                products: () {
                  cubit.navigateToProducts();
                },
                product: (product) {
                  cubit.navigateToProduct(product);
                },
                cart: () {},
              );
            },
          );
          return true;
        },

This already looks a lot cleaner than last time. First we do a check to see if we really want to go back. If the page didn't actually pop, or we're already on products, don't go back. There's nothing there for you.

Next up we need to actually change the state. We take a look at our current state. If it's products or product, we just navigate back. If it's cart, we look at the previous state and navigate to that instead.

Now let's look at our bloc listener.

BlocListener(
      cubit: cubit,
      listener: (_, HomeState state) {
        notifyListeners();
      },

When something changes, we just notify! Nice and simple. I'll put the whole build method below for context.

@override
  Widget build(BuildContext context) {
    var cubit = BlocProvider.of<HomeCubit>(context);
    return BlocListener(
      cubit: cubit,
      listener: (_, HomeState state) {
        notifyListeners();
      },
      child: Navigator(
        pages: _getPages(cubit.state, cubit.previousState),
        onPopPage: (route, result) {
          if (!route.didPop(result) || cubit.state == HomeState.products()) {
            return false;
          }
          cubit.state.when(
            products: () {
              cubit.navigateToProducts();
            },
            product: (product) {
              cubit.navigateToProducts();
            },
            cart: () {
              cubit.previousState.when(
                products: () {
                  cubit.navigateToProducts();
                },
                product: (product) {
                  cubit.navigateToProduct(product);
                },
                cart: () {},
              );
            },
          );
          return true;
        },
      ),
    );

_getPages has some simple refactoring. Since we're no longer storing the state in the navigator, we just need to tell the function what the states are!

Conclusion

I'm obviously not a perfect developer. I also violoted some practices I try to follow. Mainly DRY (Don't Repeat Yourself). I repeated state, and that was the cause of my struggles. But a small refcator later, our code is cleaner, easier to read, and works properly.

Subscribe

If an internet content creator doesn't ask for subscriptions, are they really an internet content creator?

You can follow my blog by hitting subscribe at the top, or the icon in the bottom right. Want to know what you get for free vs paid? Check out https://whitewhiskywolf.com/new-membership-and-subscription/

tl;dr Paid access will get you on the beta for the podcast app I'm building, and content will be made at least a week early to you, and can't forget ad free during that time! Otherwise free will give you email notifications when a post goes live!

Casey Daniel

Casey Daniel

Canada
Tracy Pyett

Tracy Pyett