amorenew / FlutterBlocFragment

5 stars 0 forks source link

problem on back from history #1

Open pishguy opened 5 years ago

pishguy commented 5 years ago

you suppose i click multiple on navigate to fragment X for example 20 click on that,

_fragment1 _fragment2 _fragment3 _fragment4 _fragment1 _fragment2 _fragment3 _fragment4 _fragment1 _fragment2 ...

now when i press on back button i exit application from history on two click, history should be back done.

i think problem is _currentIndex

amorenew commented 5 years ago

@MahdiPishguy I found that several backs should be unique with no duplicate for example F1 Dashboard F2 Settings F1 Dashboard Then I should have only 1 back to F2 Settings but no more backs. The stack should be a Set"Map"... Right? Later we could add 2 ways "Replace" and "Push" We should have a base fragment like dashboard then a nested Childs like "Dashboard>> "Profile" >> "Edit Profile" "Settings" "About" I will think more about the right UX how could we define a structured routes I will keep this issue open until I figure out a solid solution

pishguy commented 5 years ago

@amorenew

in profile you have another screen like with fragment? ok, so how can we manage back stack? this is very difficult to handle, may be we have more nested fragment into fragment, for example:

profile -> show profile -> update profile ..............-> edit profile -> update profile -> show profile

i think you can review instagram application

amorenew commented 5 years ago

@MahdiPishguy I think in show profile if it is the base then it should clear edit profile -> update profile because in Show profile user still can access edit button The issue is UX will confuse the user who expects go back he will go back several times to minimize the app. Chrome or web behavior is not suitable for mobile apps Still, we can have the 2 cases it is not a big issue just a boolean flag

pishguy commented 5 years ago

@amorenew yes this is not big issue, but i think is this very good for us to implementing correct way, i have another question? how to set each fragment as base? for example by default dashborad is base, how to make profile as base like with that?

amorenew commented 5 years ago

@MahdiPishguy I didn't figure out that I need R&D Currently I made a Map similar to Flutter routes

    FragmentManager().setRoutes(<String, Widget>{
      FRAGMENT_DASHBOARD: const DashboardPage(),
      FRAGMENT_DRIVER_CHECKIN: const DriverCheckInPage(),
      FRAGMENT_DRIVER_CHECKIN_CONFIRM: const DriverCheckInConfirmPage(),
      FRAGMENT_MESSAGES: const MessagesPage(),
    });

My fragment manager

import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:supervisor_flutter/widgets/fragment_bloc/fragment.dart';

class FragmentManager {

  factory FragmentManager() {
    return _singleton;
  }
  FragmentManager._internal();

  static final FragmentManager _singleton = FragmentManager._internal();

  int _currentIndex = 0;
  final List<int> _backstack = [0];

  Map<String, Widget> _fragmentsMap = <String, Widget>{};

  List<Widget> _fragments = [];

  void setRoutes(Map<String, Widget> fragmentsMap) {
    _fragmentsMap = fragmentsMap;
  }

  Map<String, Widget> getRoutes() {
    return _fragmentsMap;
  }

  void addFragment(String routeName) {
    _fragments.add(getRoutes()[routeName]);
  }

  void navigateTo(int index) {
    _backstack.add(index);
  }

  bool isExist(String fragmentName) {
    bool exist = false;
    _fragments.asMap().forEach((int index, Widget value) {
      if (value is Fragment && value.getRouteName() == fragmentName) {
        exist = true;
      } else if (value is BlocProvider &&
          value.child is Fragment &&
          (value.child as Fragment).getRouteName() == fragmentName) {
        exist = true;
      } else if (value is MultiBlocProvider &&
          value.child is Fragment &&
          (value.child as Fragment).getRouteName() == fragmentName) {
        exist = true;
      }
    });
    return exist;
  }

  int navigateToName(String fragmentName) {
    int fragmentIndex = -1;
    print('manager navigateToName:$fragmentIndex');

    _fragments.asMap().forEach((int index, Widget value) {
      if (value is Fragment && value.getRouteName() == fragmentName) {
        fragmentIndex = index;
      } else if (value is BlocProvider &&
          value.child is Fragment &&
          (value.child as Fragment).getRouteName() == fragmentName) {
        fragmentIndex = index;
      } else if (value is MultiBlocProvider &&
          value.child is Fragment &&
          (value.child as Fragment).getRouteName() == fragmentName) {
        fragmentIndex = index;
      }
    });
    print('manager navigateToName:$fragmentIndex');
    _backstack.add(fragmentIndex);
    _currentIndex = fragmentIndex;
    return fragmentIndex;
  }

  int navigateBack() {
    return --_currentIndex;
  }

  Future<bool> backPop() {
    if (_currentIndex > 0) {
      return Future<bool>.value(false);
    } else {
      return Future<bool>.value(true);
    }
  }

  void setFragments(List<Widget> fragments) {
    _fragments = fragments;
  }

  Widget getCurrentFragment(int index) {
    if (_fragments.isEmpty) addFragment(getRoutes().entries.first.key);
    return _fragments[index];
  }
}

I added extra features like AppBar stay same and it gets the title of the current fragment So AppBar and Drawer not changed.

MainAppBar(
            title: currentFragment.getTitle(),
          )

I will merge when I test more the different cases

pishguy commented 5 years ago

i have a problem now, i can't use this implementation on body of Scaffold

BlocProvider.of() called with a context that does not contain a Bloc of type FragmentBloc

change fragments on body with clicking on bottom sheet buttons

class Home extends StatefulWidget{
  @override
  State<StatefulWidget> createState()=> _HomeState();
}

class _HomeState extends State<Home> with TickerProviderStateMixin{
  AnimationController _draggableBottomSheetController;
  Duration _duration = Duration(milliseconds: 500);
  BottomBarController controller;

  @override
  void initState() {
    super.initState();
  controller = BottomBarController(vsync: this, dragLength: 550, snap: true);
  _draggableBottomSheetController = AnimationController(vsync: this, duration: _duration);
  }
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      builder: (BuildContext context)=>FragmentBloc(),
      child: Directionality(
        textDirection: TextDirection.rtl,
        child: Scaffold(
          appBar: ApplicationToolbar(
            title: Fa.appName,
          ),
          body: Dashboard(),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
          floatingActionButton: GestureDetector(
            child: FloatingActionButton(
              child: AnimatedIcon(icon: AnimatedIcons.menu_close, progress: _draggableBottomSheetController),
              elevation: 5,
              backgroundColor: Colors.deepOrange,
              foregroundColor: Colors.white,
              onPressed: () => controller.swap(),
            ),
          ),
          bottomNavigationBar: BottomExpandableAppBar(
            expandedHeight: 350,
            horizontalMargin: 3,
            bottomOffset: 10.0,
            controller: controller,
            attachSide: Side.Bottom,
            shape: AutomaticNotchedShape(RoundedRectangleBorder(), StadiumBorder(side: BorderSide())),
            expandedBackColor: Theme.of(context).backgroundColor,
            expandedBody: Center(
              child: Stack(children: <Widget>[
                Text("Hello world!"),
              ]),
            ),
            bottomAppBarBody: Container(
              color: Colors.white,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  SizedBox(width: 20.0),
                  RaisedButton(
                     color: Colors.white,
                       child: Text(
                         'مقالات سایت',
                         style: Theme.of(context).textTheme.caption.copyWith(fontFamily: 'IranSansLight', color: Colors.black),
                       ),
                       onPressed:()=>BlocProvider.of<FragmentBloc>(context).dispatch(FragmentNavigateEvent(PageScreenPosts)),
                       shape: RoundedRectangleBorder(
                           borderRadius: BorderRadius.circular(50.0))),
                  SizedBox(width: 50.0),
                  RaisedButton(
                      color: Colors.white,
                      child: Text(
                        'لیست دوره',
                        style: Theme.of(context).textTheme.caption.copyWith(fontFamily: 'IranSansLight', color: Colors.black),
                      ),
                      onPressed:()=>BlocProvider.of<FragmentBloc>(context).dispatch(FragmentNavigateEvent(PageScreenSections)),
                      shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(50.0))),
                  SizedBox(width: 20.0),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Dashboard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    FragmentManager()
      ..setFragments([ScreenPosts(),ScreenSections()]);

    return WillPopScope(
      onWillPop: () {
        BlocProvider.of<FragmentBloc>(context).dispatch(FragmentBackEvent());
        return FragmentManager().backPop();
      },
      child: BlocBuilder<FragmentBloc, FragmentState>(
          builder: (BuildContext context, FragmentState state) {
            if (state is FragmentCurrentState)
              return FragmentManager().getCurrentFragment(state.index);
            else if (state is FragmentBackState)
              return FragmentManager().getCurrentFragment(state.index);
            else
              return FragmentManager().getCurrentFragment(0);
          }),
    );
  }
}
amorenew commented 5 years ago

@MahdiPishguy I am not sure it looks fine to me If you created a sample repo I may help

pishguy commented 5 years ago

http://uplod.ir/m2puehoav73t/kelide_jazzb.7z.htm

click on green button

amorenew commented 5 years ago

@MahdiPishguy I found the issue You should use dispatch on widget under bloc builder So you need to wrap the scaffold by

 child: BlocBuilder<FragmentBloc, FragmentState>(
          builder: (BuildContext context, FragmentState state) {

Not the Dashboard

pishguy commented 5 years ago

@amorenew I fixed thanks

amorenew commented 5 years ago

@MahdiPishguy follow here if you need to add a drawer logic https://github.com/felangel/bloc/issues/407

pishguy commented 5 years ago

@amorenew there is one issue as using flag to have more fragment in backStack

amorenew commented 5 years ago

@MahdiPishguy I will do it when I have time