Flutter-Movie icon indicating copy to clipboard operation
Flutter-Movie copied to clipboard

OnLoading Boilerplate

Open henry-hz opened this issue 4 years ago • 13 comments

Hi @o1298098 , I thought that maybe, instead of using the i.e.

ctx.dispatch(CheckOutPageActionCreator.loading(true));

and creating all the boilerplate (reducer, action, dispatch), why not refactor and use the FutureBuilder instead, checking if the request is done (not null) ? I tried to write a POC with this simpler approach, but the view was not automatically rendered... so I would like to ask you if refactoring with FutueBuilder is possible, and if not, why fish-redux does not support it ? thanks for sharing your amazing app! Henry

henry-hz avatar Jul 09 '20 08:07 henry-hz

Kapture 2020-07-09 at 20 19 28 you can use FutureBuilder with fish redux, I give you a small example:

state.dart

class TestPageState implements Cloneable<TestPageState> {
  Future<ResponseModel<VideoListModel>> movies;
  @override
  TestPageState clone() {
    return TestPageState()
      ..movies = movies;
  }
}

TestPageState initState(Map<String, dynamic> args) {
  return TestPageState();
}

action.dart

enum TestPageAction { setMovies }

class TestPageActionCreator {

  static Action setMovies(Future<ResponseModel<VideoListModel>> movies) {
    return Action(TestPageAction.setMovies, payload: movies);
  }
  
}

effect.dart

Effect<TestPageState> buildEffect() {
  return combineEffects(<Object, Effect<TestPageState>>{
    Lifecycle.initState: _onInit,
  });
}

void _onInit(Action action, Context<TestPageState> ctx) {
  final _movies = TMDBApi.instance.getMovieUpComing();
  ctx.dispatch(TestPageActionCreator.setMovies(_movies));
}

reducer.dart

Reducer<TestPageState> buildReducer() {
  return asReducer(
    <Object, Reducer<TestPageState>>{
      TestPageAction.setMovies: _setMovies,
    },
  );
}

TestPageState _setMovies(TestPageState state, Action action) {
  final Future<ResponseModel<VideoListModel>> _movies = action.payload;
  final TestPageState newState = state.clone();
  newState.movies = _movies;
  return newState;
}

view.dart

Widget buildView(
    TestPageState state, Dispatch dispatch, ViewService viewService) {
  return Scaffold(
    backgroundColor: Color(0xFFF0F0F0),
    appBar: AppBar(
      title: Text('test'),
    ),
    body: FutureBuilder(
      future: state.movies,
      builder: (context, snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.none:
            return Center(child: Text('empty'));
          case ConnectionState.waiting:
          case ConnectionState.active:
            return Center(
                child: CircularProgressIndicator(
              valueColor: AlwaysStoppedAnimation(Colors.black),
            ));
          case ConnectionState.done:
            if (snapshot.hasData) {
              final ResponseModel<VideoListModel> _data = snapshot.data;
              if (_data.success)
                return ListView.separated(
                    itemBuilder: (_, index) {
                      final _d = _data.result.results[index];
                      return Container(
                          height: 30,
                          color: Colors.blue[200],
                          child: Text(_d.title));
                    },
                    separatorBuilder: (_, __) => SizedBox(
                          height: 5,
                        ),
                    itemCount: _data.result.results.length);
            }
        }
        return SizedBox();
      },
    ),
  );
}

o1298098 avatar Jul 09 '20 12:07 o1298098

@o1298098 many thanks !

henry-hz avatar Jul 09 '20 14:07 henry-hz

@o1298098 do you think I should use the same strategy to subscribe to a stream subscription like GraphQL subscriptions ? pls, see that it's possible with pure Dart or with a Flutter lib (examples)[https://github.com/dmh2000/Flutter-GraphqlX] . What strategy you would use for graphql + fish ? in fact, let me share an amazing framework for fast development with graphql: hasura.io

In fact, I noticed that you almost didn't choose to use FutureBuilder, even that it's a quite less boilerplate, so let me ask you why ? :)

Flutter-Movie git/master~19*  2262s
❯ grep -rni futurebuilder ./lib
./lib/views/start_page/view.dart:58:    body: FutureBuilder(
./lib/customwidgets/share_card.dart:119:    return FutureBuilder<Widget>(
./lib/customwidgets/medialist_card.dart:228:            child: FutureBuilder<UserListModel>(
./lib/customwidgets/searchbar_delegate.dart:62:    return FutureBuilder<SearchResultModel>(
./lib/customwidgets/searchbar_delegate.dart:95:    return FutureBuilder<List<String>>(
./lib/customwidgets/searchbar_delegate.dart:185:    return FutureBuilder<SearchResultModel>(

henry-hz avatar Jul 12 '20 07:07 henry-hz

Yes, you should, GraphQL Engine is an amazing project. GraphQL is more flexible in obtaining data and is a good choice for mobile applications. I also plan to use GraphQL in new projects. If you want to modify the data in Redux, you can only call Reducer, then the UI will be updated. but this state type is Future, if I want to load more items in the listview , it is not convenient to use FutureBuilder

o1298098 avatar Jul 13 '20 01:07 o1298098

@o1298098 great tip! thanks :) In fact, in case I am asking too much here, let me know if you would have availability to create a profile in https://www.codementor.io , and we close an hourly consulting, so I can also feel free to ask specific questions about my project.

To update the language, I noticed that you started to write a ./lib/style/themestyle_widget.dart , but in fact, it's not connected to the main code. I am trying to implement the automatically language reload using a Locale in GlobalState and calling

I18n.onLocaleChanged(newLocale);

As described in the i18n plugin. It's in the reducer, but maybe because the Locale state is outside, it doesn't refresh as in this application.

Do we have a way to refresh the context for ALL application, in a way that the Language could be updated dynamically during the runtime ? (it works only for the screen painted with the language selector) thanks!

henry-hz avatar Jul 13 '20 11:07 henry-hz

Already solved, I was lacking the

if (p.locale != appState.locale) { 

In the router visitor to propagate the global state.... In fact, I think it's a glue, it should be an intrinsic part of the framework..... yep, they already noticed: https://github.com/alibaba/fish-redux/issues/549

henry-hz avatar Jul 13 '20 12:07 henry-hz

I have tried this before, but it didn't work as expected.

o1298098 avatar Jul 13 '20 13:07 o1298098

Instead of 'Future<Widget> createApp() async {' in the app.dart file, try to create a Statefull Widget (it's also better for refreshing, as I wrote you before).

import 'package:diga1/config/themes.dart';                                                                                                                                                                                                              [0/327]
import 'package:diga1/helpers/persistor.dart';
import 'package:diga1/global/store.dart';

/// Main application
class App extends StatefulWidget {
  App({Key key}) : super(key: key);

  @override
  _AppState createState() => _AppState();
}

class _AppState extends State<App> {
  // see https://api.flutter.dev/flutter/material/MaterialApp-class.html for more info
  // home_page is configured in Router to be the landing page, and we use it when
  // building the MaterialApp widget, in 'home' property.
  final AbstractRoutes routes = Routes.routes;

  /// Initialize secure persistance with a signleton for a future use
  ///   ```dart
  ///   Persistor.write('jwt_token', 'my_token');
  ///   Persistor.read('jwt_token').then((t) => print(t));
  /// ```

  /// Main Application Initialization
  /// There are two reasons why you need to wrap MaterialApp
  /// Because this ensures that toast can be displayed in front of all other controls
  /// Context can be cached so that it can be invoked anywhere without passing in context
  final i18n = I18n.delegate;


  /// Dynamically change the language and instantly show the new language
  /// see [i18-plugin](https://github.com/esskar/vscode-flutter-i18n-json)
  @override
  void initState() {
    Persistor.getInstance();
    I18n.onLocaleChanged = onLocaleChange;
    super.initState();
  }

  void onLocaleChange(Locale locale) {
    setState(() {
      I18n.locale = locale;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Diga1',
      localizationsDelegates: [
        i18n,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: i18n.supportedLocales,
      localeResolutionCallback: i18n.resolution(fallback: new Locale("en", "US")),
      debugShowCheckedModeBanner: true,
      theme: Themes.lightTheme,
      // we send a Map<String, dynamic> arguments to initialize the State
      // e.g. home: routes.buildPage('home_page', {'name': 'john', 'age': 32}),
      home: routes.buildPage('start_page', {'name': 'Diga1'}),
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute<Object>(builder: (BuildContext context) {
          return routes.buildPage(settings.name, settings.arguments);
        });
      },
    );

  }

}

on the Global Store, first we update the Locale in the onLocaleChanged callback, and afterwards we send the cloned state with a new locale only to propagate.

GlobalState _onChangeLocale(GlobalState state, Action action) {
  final Locale locale = action.payload;
  I18n.onLocaleChanged(locale);
  return state.clone()..locale = locale;
}

Notice that in fact, the locale 'lives' in the i18n.dart file, but we propagate it also from the global, so it will trigger a global refresh.

 page.connectExtraStore<GlobalState>(GlobalStore.store,
            (Object pagestate, GlobalState appState) {
              final GlobalBaseState p = pagestate;
              //print(appState.user?.toJson());
              if (p.locale != appState.locale) {
                if (pagestate is Cloneable) {
                  final Object copy = pagestate.clone();
                  final GlobalBaseState newState = copy;
                  newState.locale = appState.locale;
                  return newState;
                }
              }
              if (p.user != appState.user) {
                if (pagestate is Cloneable) {
                  final Object copy = pagestate.clone();
                  final GlobalBaseState newState = copy;
                  newState.user = appState.user;
                  return newState;
                }
              }
              return pagestate;
            });
      }
    }

and also on the state of the views you want to 'refresh'

class LoginState implements GlobalBaseState, Cloneable<LoginState> {
  // LOGIN form
  String emailAddress;
  String password;
  TextEditingController emailTextController;
  TextEditingController passwordTextController;

  // FORGOT PASSWORD Form
  String emailReset;
  TextEditingController forgotTextController;
  // for the globalbasestate
  @override
  Locale locale;

  @override
  User user;

  @override
  LoginState clone() {
    return LoginState()
      ..emailTextController = emailTextController
      ..passwordTextController = passwordTextController
      ..forgotTextController = forgotTextController
      ..user = user;
  }

}

LoginState initState(Map<String, dynamic> args) {
  return LoginState();
}

henry-hz avatar Jul 13 '20 13:07 henry-hz

thanks @henry-hz, it work😄

o1298098 avatar Jul 13 '20 14:07 o1298098

@henry-hz, I created a GraphQL Api for this project, and now I can get data by subscription. I plan to replace some widgets with StreamBuilder. Kapture 2020-07-24 at 10 36 58

o1298098 avatar Jul 24 '20 02:07 o1298098

@o1298098 hey, that's good news, very powerful real-time, without touching the web-socket 101 upgrade... : ) I will see the code. What is the tool that you used above to send the graph queries ?

henry-hz avatar Jul 26 '20 10:07 henry-hz

@henry-hz, GraphQLPlayground

o1298098 avatar Jul 27 '20 01:07 o1298098

Kapture 2020-07-27 at 20 43 31 @henry-hz, It works perfectly😄

o1298098 avatar Jul 27 '20 12:07 o1298098