felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.78k stars 3.39k forks source link

question: How to use Navigator2 and bloc with flutter is 1.22 #1816

Closed huang12zheng closed 4 years ago

huang12zheng commented 4 years ago

my code:

return BlocProvider(
      create: (context) => MessagePageBloc(),
      child: BlocBuilder<MessagePageBloc, MessagePageState>(
        builder: (context, state) => ModualRoute(
          children: [
            MessageSceen(),
            if (state is! Home) state.buildWidget(),
          ]
        )
      ),
    );

class ModualRoute extends StatelessWidget {
  const ModualRoute({
    Key key,
    @required this.children,
    this.onPopPage,
    this.refresh
  }) : super(key: key);

  final List<Widget> children;
  final PopPageCallback onPopPage;
  final VoidCallback refresh;

  @override
  Widget build(BuildContext context) {
    return Navigator( /// this is navigator
      pages: [
        for (Widget child in children) toPage(child)
      ],
      onPopPage: onPopPage == null 
        ? (route, result) {
          if (!route.didPop(result)) {
            return false;
          }

          // Update the list of pages by setting _selectedBook to null
          refresh?.call();

          return true;
        }
        : onPopPage,
    );
  }
}

Can I stop using BlocListener ?

felangel commented 4 years ago

Hi @huang12zheng 👋 Thanks for opening an issue!

With the Pages API you can use a BlocBuilder to handle navigation in response to state changes. The following is an example just to demonstrate the concept but I generally don't recommend having events like push/pop and tightly coupling your bloc to navigation.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => FlowBloc(),
        child: Flow(),
      ),
    );
  }
}

class Flow extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<FlowBloc, FlowState>(
      builder: (context, state) {
        return Navigator(
          pages: [
            if (state.index >= 0) MaterialPage<void>(child: PageA()),
            if (state.index >= 1) MaterialPage<void>(child: PageB()),
          ],
          onPopPage: (route, dynamic result) {
            context.bloc<FlowBloc>().add(FlowEvent.pop);
            return false;
          },
        );
      },
    );
  }
}

class PageA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('A')),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.arrow_forward),
        onPressed: () => context.bloc<FlowBloc>().add(FlowEvent.push),
      ),
    );
  }
}

class PageB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('B')),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.arrow_back),
        onPressed: () => context.bloc<FlowBloc>().add(FlowEvent.pop),
      ),
    );
  }
}

enum FlowEvent { push, pop }
enum FlowState { a, b }

class FlowBloc extends Bloc<FlowEvent, FlowState> {
  FlowBloc() : super(FlowState.a);

  @override
  Stream<FlowState> mapEventToState(FlowEvent event) async* {
    switch (event) {
      case FlowEvent.push:
        yield state.index + 1 < FlowState.values.length
            ? FlowState.values[state.index + 1]
            : state;
        break;
      case FlowEvent.pop:
        yield state.index > 0 ? FlowState.values[state.index - 1] : state;
        break;
    }
  }
}

Hope that helps 👍

Closing for now but feel free to comment with additional questions and I'm happy to continue the conversation 😄

huang12zheng commented 4 years ago
abstract class RouteState with EquatableMixin {
  const RouteState():super();
  String get id;
  Widget buildWidget();

  @override
  List<Object> get props => [runtimeType, id]; 
}

abstract class SimpleRouteState extends RouteState {
  const SimpleRouteState():super();

  @override
  String get id => '';

}

class RouteTreeState with EquatableMixin{
  final List<RouteState> values;

  RouteTreeState([this.values= const [] ]);
  @override
  List<RouteState> get props => values;

  List<Widget> buildWidget() {
    return <Widget>[
      for (RouteState state in values) state.buildWidget()
    ];
  }

  List<Page> buildRoute() {
    return <Page>[
      for (RouteState state in values) toPage(state.buildWidget())
    ];
  }

}

class NavigatorCubit extends Cubit<RouteTreeState> {
  NavigatorCubit(RouteTreeState initState) : super(initState);

  void push(RouteState newState) =>
    emit(RouteTreeState([
      ...state.values..remove(newState),
      newState,
    ]));

  void pop()=> emit(RouteTreeState(
    [...state.values]..removeLast(),
  ));
}

VoidCallback cubitNav<T extends NavigatorCubit>(BuildContext context,RouteState state) {
  return ()=>context.bloc<T>().push(state);
}

class RouteTreeBuilder<T extends NavigatorCubit> extends StatelessWidget {
  const RouteTreeBuilder({
    Key key,
    @required this.create,
    // @required this.homeScreen,
    this.onPopPage,
  }) : super(key: key);
  final CreateBloc<T> create;
  // final Widget homeScreen;
  final PopPageCallback onPopPage;

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: create,
      child: BlocBuilder<T, RouteTreeState>(
        builder: (builderContext, state) => Navigator(
          pages: state.buildRoute(),
          onPopPage: onPopPage ?? (route, result) {
            if (!route.didPop(result)) return false;

            // Update the list of pages by setting _selectedBook to null
            builderContext.bloc<T>().pop();

            return true;
          }
        )
      )
    ); 
  }
}