Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OnLoading Boilerplate #31

Open
henry-hz opened this issue Jul 9, 2020 · 13 comments
Open

OnLoading Boilerplate #31

henry-hz opened this issue Jul 9, 2020 · 13 comments

Comments

@henry-hz
Copy link

henry-hz commented Jul 9, 2020

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

@o1298098
Copy link
Owner

o1298098 commented Jul 9, 2020

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();
      },
    ),
  );
}

@henry-hz
Copy link
Author

henry-hz commented Jul 9, 2020

@o1298098 many thanks !

@henry-hz
Copy link
Author

henry-hz commented Jul 12, 2020

@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>(

@o1298098
Copy link
Owner

o1298098 commented Jul 13, 2020

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

@henry-hz
Copy link
Author

henry-hz commented Jul 13, 2020

@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
Copy link
Author

henry-hz commented Jul 13, 2020

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: alibaba/fish-redux#549

@o1298098
Copy link
Owner

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

@henry-hz
Copy link
Author

Instead of 'Future 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();
}

@o1298098
Copy link
Owner

thanks @henry-hz, it work😄

@o1298098
Copy link
Owner

@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

@henry-hz
Copy link
Author

@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 ?

@o1298098
Copy link
Owner

@henry-hz, GraphQLPlayground

@o1298098
Copy link
Owner

o1298098 commented Jul 27, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants