📜 ⬆️ ⬇️

Detecting dependencies of Android components

This is not another article about Dagger and its features. There will not be a word about other DI frameworks.

scan

The purpose of this publication is to demonstrate an approach to obtaining dependencies in fragments, dialogs and activations.

Setting listener for dialogue


In one of the inherited projects, I came across the following dialogue implementation:

public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } @Override public void onAttach (Context context) { super.onAttach(context); if(context instanceOf Listener) { listener = (Listener) context; } else { listener = (Listener) getParentFragment(); } } } 

“And since when does a component have to look for a listener,” I thought at that moment.
Let's make the fragment not know who specifically implements the listener interface.
Many can immediately suggest, for example, this option:
')
 public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(Listener listener) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); dialogFragment.listener = listener; return dialogFragment; } } 

and the activation code into which we will embed this dialog:

 public class ExampleActivity extends AppCompatActivity { void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new DialogFragment.Listener() { @Override void onMessageEntered(String msg) { // TODO } }); dialogFragment.show(getFragmentManager(), "dialog"); } } 

This solution has one major drawback. When changing the configuration (for example, flipping the screen), we get the following chain: the dialog will save its state in the Bundle and will be destroyed -> activations will be deleted -> a new instance of activations will be created -> the dialog will be recreated based on the state saved in the Bundle . As a result, we will lose the link to the listener in the dialogue, since it was clearly not saved and restored. setListener() course, we can manually call setListener() in one of the callbacks of the activation life cycle, but there is another option. Since we cannot save an anonymous class in the Bundle , as well as instances of regular classes, we need to meet the following conditions:

  1. The listener must implement a Serializable or Parcelable
  2. Pass the listener through the fragmet's arguments when creating it setArguments(Bundle args)
  3. The listener must be stored in the onSaveInstanceState(Bundle outState) method onSaveInstanceState(Bundle outState)
  4. The listener must be restored in the Dialog onCreateDialog(Bundle savedInstanceState) method Dialog onCreateDialog(Bundle savedInstanceState)

As a rule, the listener interface is implemented by such Android components as Activity or Fragment . Such components are not intended to be saved in the Bundle , so we need to find a different approach to the solution. Let's try not to transmit the listener himself, but the “ Provider” who is able to find him. In this case, no one will hurt us to make it serializable and save it in the Bundle .

If our “detective” does not have to change its state in the process of interacting with the component, then you can not override the onSaveInstanceState(Bundle outState) method and restore the dependency from the arguments by calling the Dialog onCreateDialog(Bundle savedInstanceState) method.

Let's look at the implementation:

 public class ExampleDialogFragment extends DialogFragment { private static final String LISTENER_PROVIDER = "listener_provider"; private Listener listener; public interface ListenerProvider extends Serializable { Listener from(DialogFragment dialogFragment); } public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(ListenerProvider provider) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); Bundle args = new Bundle(); args.putSerializable(LISTENER_PROVIDER, provider); dialogFragment.setArguments(args); return dialogFragment; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle args = getArguments(); if(args == null || !args.containsKey(LISTENER_PROVIDER)) { throw new IllegalStateException("Listener provider is missing"); } ListenerProvider listenerProvider = (ListenerProvider) args.getSerializable(LISTENER_PROVIDER); Listener listener = listenerProvider.from(this); ... } } 

In this case, our activation code will look like:

 public class ExampleActivity extends AppCompatActivity implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getActivity(); } } } 

If we need to implement the display of the dialogue from the fragment, then we get the following code:

 public class ExampleFragment extends Fragment implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getParentFragment(); } } } 

As a result, we get that the calling component itself helps the dialogue to find the listener. At the same time, the fragment has no idea who exactly it (the listener) is. For example, if for any reason we do not want to contact the Activity directly, then no one bothers to just throw the event and then catch it in the right place and process it (the dialog code will not even need to be changed):

 public class ExampleFragment extends Fragment { void onMessageEvent(Message message) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class Message { public final String content; private Message(String content) { this.content = content; } } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return new ExampleDialogFragment.Listener() { @Override public void onMessageEntered(String msg) { EventBus.getDefault().post(new Message(msg)); } }; } } } 

With dialogues sort of sorted out. We go further.

Search dependencies in fragments


Often it is necessary to organize interaction of type Activity <-> Fragment or Fragment <-> Fragment within the framework of one activit. The general principle remains the same as was described above: through the interface (for example, Listener) and the “detective”, communication between the components is organized. In this article we will consider one-way interaction.

As an example, consider the case of obtaining a presenter in the fragment. I think each of us came across a similar:

 public interface Presenter { ... } public class ExampleFragment extends Fragment { private Presenter presenter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = App.get().getExampleFragmentComponent().getPresenter(); } } 

And everything seems to be good, we hid the creation of dependencies, but similar receipt of them is now scattered throughout the project. In my opinion, at least the one who causes should either provide these dependencies or help them find them.

Again, apply our reception with the "detective":

 public class ExampleFragment extends Fragment { private static final String DI_PROVIDER = "di_provider"; private Presenter presenter; public interface DependencyProvider implements Serializable { Presenter getPresenterOf(Fragment fragment); } public static Fragment newInstance(DependencyProvider dependencyProvider) { Fragment fragment = new ExampleFragment(); Bundle args = new Bundle(); args.putSerializable(DI_PROVIDER, dependencyProvider); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); if(args == null || !args.containsKey(DI_PROVIDER)) { throw new IllegalStateException("DI provider is missing"); } DependencyProvider diProvider = (DependencyProvider) args.getSerializable(DI_PROVIDER); presenter = diProvider.getPresenterOf(this); } } public class ExampleActivity extends AppCompatActivity { void showFragment() { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment fragment = ExampleFragment .newInstance(new DiProvider()); ft.add(R.id.container, fragment); ft.commit(); } private static class DiProvider implements ExampleFragment.DependencyProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public Presenter get(Fragment fragment) { return App.get().getExampleFragmentComponent().getPresenter(); } } } 

Thus, we made our fragment more universal; accordingly, we can easily transfer it from a project to a project without changing the component code without need.

In a similar way, you can organize getting dependencies in the Activity .

A small example with the implementation of this approach is here .

I hope the described approach will be useful to you in the implementation of projects and will be useful.
Thanks for attention!

Source: https://habr.com/ru/post/342038/


All Articles