Skip to content
New issue

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

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

Already on GitHub? # to your account

Force onModelReady #15

Closed
eskalera opened this issue Jul 12, 2019 · 7 comments
Closed

Force onModelReady #15

eskalera opened this issue Jul 12, 2019 · 7 comments

Comments

@eskalera
Copy link

I am using your BaseModel-BaseWidget arquitecture from 014-provider-v3-updates.
I have the following:

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    AppModule module = Provider.of<MyModel>(context).appModule;
    return BaseWidget<MyPageViewModel>(
        onModelReady: (model) => model.fetchData(module),
        model: MyPageViewModel(api: Provider.of(context)),
        builder: (context, model, child) => model.busy
            ? Center(
                child: CircularProgressIndicator(),
              )
            : Scaffold(

When the module changes I wanted my data to be fetched again, but I see the BaseWidget is only being rebuilt, without calling the onModelReady. I guess this is because the viewModel has not been disposed, right?

How can I approach these scenario?

@FilledStacks
Copy link
Owner

Hi there,

This architecture was specifically set up to have a function that can be called once upon the creation of a widget. So in this case you'd have to extend or change the architecture for your purpose.

If you want to call a function on the model every time it's rebuilt you should remove the model from your base widget state and pass it in directly to the builder function using widget.model, instead of setting it in the init function to a local long living model. Then on the outside you'll pass it in like this, and call your fetchData after it's been constructed.

AppModule module = (...)
BaseWidget<MyPageViewModel>(
        model: MyPageViewModel(api: Provider.of(context))..fetchData(module),
        builder: ...
)

@eskalera
Copy link
Author

Hi,

Thanks for your quick response. I am trying to do what you suggest:

class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
  final T model;
  final Widget child;
  final Function(T) onModelReady;
  final bool fetchOnBuild;

  BaseWidget(
      {Key key,
      this.builder,
      this.model,
      this.child,
      this.onModelReady,
      this.fetchOnBuild})
      : super(key: key);

  _BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}

class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
  T model;

  @override
  void initState() {
    model = widget.model;

    if (widget.onModelReady != null) {
      widget.onModelReady(model);
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<T>(
      builder: (context) => widget.fetchOnBuild ?? false ? widget.model : model,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }
}

As you can see, my way of implementing this is to add a boolean variable fetchOnBuild, if true I return the widget.model in the builder. I can see the constructor of the BaseWidget is being invoked, but the build method of the _BaseWidgetState is not being called.

@FilledStacks
Copy link
Owner

Mmmmmmmm. I think you should split up this here

  builder: (context) => widget.fetchOnBuild ?? false ? widget.model : model,

Because what I'm seeing there is that when widget.fetchOnBuild is supplied it will return that value. If not it will return false, which will then return the model. So it'll either return true if fetchOnBuild is supplied or the model internally, but I can't see it returning your widget.model.

Split that up and make it more readable.

builder: (context) {
 if(widget.fetchOnBuild != null && widget.fetchOnBuild) {
   return widget.model;
 }

  return model;
}

Something like that maybe?

@eskalera
Copy link
Author

What that line says is that it returns widget.model when fetchOnBuild is true and it returns model if fetchOnBuild is false or undefined.

Anyway, I ended up implementing another function next to OnModelReady

class BaseWidget<T extends ChangeNotifier> extends StatefulWidget {
  final Widget Function(BuildContext context, T model, Widget child) builder;
  final T model;
  final Widget child;
  final Function(T) onModelReady;
  final Function(T) onRebuild;

  BaseWidget({
    Key key,
    this.builder,
    this.model,
    this.child,
    this.onModelReady,
    this.onRebuild,
  }) : super(key: key);

  _BaseWidgetState<T> createState() => _BaseWidgetState<T>();
}

class _BaseWidgetState<T extends ChangeNotifier> extends State<BaseWidget<T>> {
  T model;

  @override
  void initState() {
    model = widget.model;

    if (widget.onModelReady != null) {
      widget.onModelReady(model);
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    if (widget.onRebuild != null) widget.onRebuild(model);

    return ChangeNotifierProvider<T>(
      builder: (context) => model,
      child: Consumer<T>(
        builder: widget.builder,
        child: widget.child,
      ),
    );
  }
}

Thank you very much for your help and for all your work! We have chosen your arquitecture for a complex, production-ready full application and my team is really happy with it. Congratulations!

@FilledStacks
Copy link
Owner

Haha I struggle with code like that so I completely miss read it . Sometimes it's a bit too short hand.

That's super awesome man!! I'm so happy that I could help.

I like the approach you took above. I didn't think of that solution and I like it! Thanks for sharing

@elhe26
Copy link

elhe26 commented Sep 19, 2019

Hi @eskalera ,

Could you please do an example of an implementation of this BaseView and data fetching? I'm trying to wrap my head but I don't know what parameters I need to pass.

Regards,

@eskalera
Copy link
Author

Hi @ericklhe ,

I sure do. Here is an example of the build method of a widget:

@override
  Widget build(BuildContext context) {
    return BaseWidget<MyViewModel>(
        onRebuild: (model) => model.fetchData(),
        onModelReady: (model) => this.model = model,
        model: MyViewModel(api: Provider.of(context), user: user),
        builder: (context, model, child) => Container());
  }

It's just the exact same as proposed by @FilledStacks , just changing onModelReady for onRebuild

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

No branches or pull requests

3 participants