📜 ⬆️ ⬇️

Android and Data Binding: action handling

Not so long ago, we finished developing an application in which we had to handle the same actions (actions) in various places of the application. It would seem that the standard situation, and, as always, the developers are lazy, the client - do everything yesterday, our client has a client, and he needs everything the day before yesterday. So, you need to do everything simply, beautifully and, most importantly, fewer extra lines of code.

A feature of the project was that most of the screens contained the same or very similar entities and allowed users to perform a series of identical actions on them. How we solved this problem will be discussed in this article.

Initial data


If you know what Instagram is, then by adding the words “closed, corporate” you will be able to clearly present the essence of our project. In short, it is a closed social network with the ability to post short notes with photo or video content, view them, comment, “like”, add authors to the list of favorites and track their publications separately, search for friends, search for publications, etc. Each publication may belong to different regions and be in one or several categories.

Total we have several screens (“screenshots”): a list of publications for a specific category, a detailed description of a publication, a list of comments, a list of users to be monitored (following), their publications, a list of those who follow you (followers), a profile user profile with a bunch of ratings , counters, avatar, etc.
')
Almost on each screen there are avatars of users (either the actual list of users, or the publication with the avatar and the name of the author, or the comment of a particular user). Also, there are Follow / Unfollow, Like / Dislike buttons on different screenshots, category tags and others.

Clicking on the avatar you need to open a user profile. Clicking on the article - open its details. Clicking on the icon "Like" or "Follow" - well, you understand ...

Approach


Approaching the case in the usual way, the case is not solved so and difficult. For example, on a click we open a user profile:

findViewById(R.id.some_id).setOnClickListener((v) -> openUserProfile()); void openUserProfile(){ Intent = new Intent(this, ProfileActivity.class); intent.putExtra(USER_ID, userId); startActivity(); } 

In the case of “Like” or “Follow” it is already more difficult, you need to make a request to the server, wait for its response and, depending on the result, change the presentation of the button in a specific list item. In principle, nothing too complicated. But, since both “Like” and “Follow” can be in many places of the application, in order to facilitate reuse, it is logical to delegate their processing to individual classes, which was finally done. Such action handlers are called “Action” (FollowUserAction, LikeAction, OpenProfileAction, ...). All actions processed on a specific screen are collected and run through a certain ActionHandler manager. As a result, opening the same user profile screen will look like this:

  mActionHandler = new ActionHandler.Builder() .addAction(ActionType.PROFILE, new OpenProfileAction()) .build(); findViewById(R.id.some_id).setOnClickListener((v) -> openUserProfile()); ... void openUserProfile(){ mActionHandler.fireAction(ActionType.PROFILE, user); } 

Ok, go ahead. To further reduce the amount of unnecessary code, we include support for Android Data Binding and in the business logic code we leave only the ActionHandler. What action to perform and by clicking on which button we write in the layout file itself. For example, for a screen with a list of publications, we have:

  mBinding = DataBindingUtil.inflate(..., R.layout.item_post); mBinding.setActionHandler(getActionHandler()); mBinding.setPost(getPost()); void initActionHandler() { mActionHandler = new ActionHandler.Builder() .addAction(ActionType.PROFILE, new OpenProfileAction()) .addAction(ActionType.COMMENTS, new OpenCommentsAction()) .addAction(ActionType.POST_DETAILS, new OpenPostDetailsAction()) .addAction(ActionType.FOLLOW, new FollowUserAction()) .addAction(ActionType.LIKE, new LikeAction()) .addAction(ActionType.MENU, new CompositeAction((TitleProvider)(post) -> post.getTitle(), new ActionItem(ActionType.SOME_ACTION_1, new SomeMenuAction(), "Title 1"), new ActionItem(ActionType.SOME_ACTION_2, new SomeMenuAction(), "Title 2"), new ActionItem(ActionType.SOME_ACTION_3, new SomeMenuAction(), "Title 3"), .build(); } 


item_post.xml
 <layout> <data> <variable name="actionHandler" type="com.example.handler.ActionHandler" /> <variable name="post" type="com.example.model.Post" /> </data> <FrameLayout> <ImageView android:id="@+id/avatar" ... app:actionHandler="@{actionHandler}" app:actionType="@{ActionType.PROFILE}" app:model="@{post}" /> <FrameLayout android:id="@+id/post_container" ... app:actionHandler="@{actionHandler}" app:actionType="@{ActionType.POST_DETAILS}" app:actionTypeLongClick="@{ActionType.MENU}" app:model="@{post}"> ... </FrameLayout> <TextView android:id="@+id/comments" ... app:actionHandler="@{actionHandler}" app:actionType="@{ActionType.COMMENTS}" app:model="@{post}" /> <ImageView android:id="@+id/like" ... app:actionHandler="@{actionHandler}" app:actionType="@{ActionType.LIKE}" app:model="@{post}" /> ... </FrameLayout> </layout> 

Now, if, for example, on some screen, you need to block the opening of a profile by clicking on it, or add / remove a menu item displayed by a long press ( actionTypeLongClick = "@ {ActionType.MENU} ), all you need to do is add or delete in one place the corresponding Action.

Using Data Binding also allows you to change the model from the Action itself (for example, add a “like”) and immediately see the changes on the screen without any additional callbacks calling notifyDataSetChanged () for RecyclerView.

Here are some examples of action:

  public class OpenProfileAction extends IntentAction<IUserHolder> { @Override public boolean isModelAccepted(Object model) { return model instanceof IUserHolder; } @Nullable @Override public Intent getIntent(@Nullable View view, Context context, String actionType, IUserHolder model) { return ProfileActivity.getIntent(context, model); } @Override protected ActivityOptionsCompat prepareTransition(Context context, View view, Intent intent) { // Prepare shared element transition Activity activity = getActivityFromContext(context); if (activity == null) return null; return ActivityOptionsCompat .makeSceneTransitionAnimation(activity, view, ProfileActivity.TRANSITION_NAME); } } public class LikeAction extends RequestAction<ModelResponse<Like>, Post> { @Override public boolean isModelAccepted(Object model) { return model instanceof Post; } @Override protected Observable<ModelResponse<Like>> getRequest(Context context, RestApiClient apiClient, Post model) { return apiClient.setLike(model.postId, !model.isLiked); } protected void onSuccess(Context context, View view, String actionType, Post oldModel, ModelResponse<Like> response) { oldModel.setLiked(response.getModel().isLiked); // automatically rebind icon for "like" button } } 


Results


Thus, it turned out to organize a very flexible and, I hope, quite understandable logic of processing actions in the application.

As a result, the idea developed, passed two other projects, and, in the end, it was embodied in a small library - action-handler .

Now there are blanks in it for such frequent action:


Links


Library with simple examples - https://github.com/drstranges/ActionHandler

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


All Articles