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

Abstraction layer for implementing functionalities #111

Open
JakubNeukirch opened this issue May 2, 2021 · 0 comments
Open

Abstraction layer for implementing functionalities #111

JakubNeukirch opened this issue May 2, 2021 · 0 comments
Labels
enhancement New feature or request

Comments

@JakubNeukirch
Copy link

We need some abstraction layer, to easily implement business logic without any interaction with generated files.

I have already a few ideas and I can contribute but firstly I would like to show what I am thinking about. And also I want to verify my ideas with other people opinion.

How to implement your own features

So let me begin with how will UI operate with custom code. Basically we would have Features abstract class in adobe_xd plugin. Its responsibility is to invoke custom code, provide UI data and return processed data to the view.

abstract class Features<DATA> {
  final ViewDataGetter viewDataGetter;
  Map<String, dynamic> get viewData => viewDataGetter();
  OnData<DATA> _onData = (_){};

  void init(OnData<DATA> onData) {
    _onData = onData;
  }

  void setData(DATA data) {
    _onData(data);
  }

  void dispose() {}

  Features(this.viewDataGetter);
}

So what we have here is:

  • ViewDataGetter lambda which normally is reference to a method returning UI data.
  • OnData also reference to method from UI - it is invoked when processed data is changed.

Each UI generates its own Features for example Login Page would have features like this:

abstract class LoginFeatures extends Features<LoginData> {
  void login();

  LoginFeatures(ViewDataGetter getter): super(getter);
}

class LoginData {
  final LoginState state;

  LoginData(this.state);
}

The method login() was generated based on Tap Callback of a button from Adobe XD.
LoginData is also generated - I do not have clear view how it will look yet, but probably it will be based on parameters or some additional settings of a page.
And yours implementation would look like this:

class LoginFeaturesImpl extends LoginFeatures {
  final AuthApi api;

  LoginFeaturesImpl(ViewDataGetter getter, this.api):super(getter);

  @override
  void login() async {
      try{
        setData(LoginData(LoginState.loading));
        await api.login(viewData["email"], viewData["password"]);
        setData(LoginData(LoginState.success));
      }catch(error) {
        setData(LoginData(LoginState.error));
      }
  }
}

As you can see we invoke setData() to provide UI some updates based on our features. Also as you can see, you can easily take data from view through viewData["email"]. Through viewData you can also provide BuildContext if needed for some context-based operations - also we might consider adding BuildContext to every method so it will be always context from specific place in widget tree. Something like this void login(BuildContext context).

How to Provide your own Features class

For providing and injecting implementation of Features class you can use FeaturesProvider - for now it is very simple, but we may think of something more complex like BlocProvider.

FeaturesProvider would be a part of adobe_xd plugin. It contains all implementations of Features and injects them to UI.
Usage of it would look like this (in main.dart file):

MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primaryColor: XDColors.primary,
        accentColor: XDColors.accent,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: FeaturesProvider(
          child: XDLoginScreen(),
          features: {
            LoginFeatures: (getter) => LoginFeaturesImpl(getter, AuthApi())
          },
      ),
    );

Argument features is just a Map<Type, Features>. Based on Type, Features implementation is injected in UI. Additional code that will be generated in UI (in this case XDLoginScreen) looks like this:

  LoginFeatures features;
  //here will be all fields from Data class
  LoginState state;

  @override
  void initState() {
    super.initState();
    features = FeaturesProvider
        .of(context)
        .getFeatures(_getViewData);
        WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
          features.init(onData);
        });
  }

  @override
  void dispose() {
    features.dispose();
    super.dispose();
  }

  void onData(LoginData data) {
      setState(() {
        state = data.state;
      });
  }

  Map<String, dynamic> _getViewData() {
    return {
      "login": _loginController.text,
      "password": _passwordController.text
    };
  }

As you probably noticed - the generated view was changed to StatefulWidget. That allows us to properly initialize and operate on Features and update UI when onData is invoked.
The thing to be thought through is UI data providing. For now I used TextEdittingController but it is too case-specific. We may think about creating some DataHolder<TYPE> or something like this, so it will be more generic. Such DataHolder would be placed in components which can modify its data.

Summary

There might be some holes in this whole idea. But I am placing it here so we could polish the idea together and come up with some great solution. Maybe someone has more simple solution?

@JakubNeukirch JakubNeukirch added the enhancement New feature or request label May 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant