User Content (UC)

ECn Categories > User Content (UC) > Partially Blocked (PB)

The user has some content or work done, and because of a connection problem it is lost by the app.

Amount of Issues App List
1 (A39) Habitica

Examples

Habitica

Category: Productivity
v 1.1.6
Scenario: To send a message in the chat without internet connection
This video shows an example of a Lost User Content, at second 4 the user has type a message and send it at second 6, after that knowing that the device is no connected to internet, the message dissapear from the user interface. No matter if the user refresh the chat or change the activity and comeback the message get lost.

The following code snippets show the classes and files that are involved in the generation of the previuos issue.

In order to review this example, we are going to start reviewing where the new message event is created, and for this its neccesary to see in the following snippet the definition of the layout used in the chat activity to create a new message, in specific we can see at between lines 36-44 the button used to send a new message and between 27-35 the input field used to write the message.

Habitica/res/layout/tavern_chat_new_entry_item.xml

    <android.support.v7.widget.AppCompatEditText
28.     android:id="@+id/chatEditText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_margin="5dp"
        android:hint="@string/write_message"
        android:textColor="@android:color/black"
        android:inputType="textCapSentences|textMultiLine"/>
    <ImageButton
37.     android:id="@+id/sendButton"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_gravity="center"
        android:background="@color/transparent"
        android:tint="@color/md_grey_400"
        android:src="@drawable/ic_send_grey_600_24dp"
        android:contentDescription="@string/send"/>

Now that we know how the view is defined, we need to see how this elements are used inside the code to create the new message event. First, the following snippet shows the binding between the view elements and variables from the event manager for the chat. The first highlighted line shows the button binding, and the second one the input field binding.

Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt

    private val chatBarContainer: LinearLayout by bindView(R.id.chatBarContainer)
22. private val sendButton: ImageButton by bindView(R.id.sendButton)
23. private val chatEditText: EditText by bindView(R.id.chatEditText)

To continue with the variable definition this class stores in a variable at line 27 the action that will be executed when the button is clicked, this variable is defined as a high-order function that receives a string and returns an Unit element.

Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt

    private fun setupView(context: Context) {
        val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        inflater.inflate(R.layout.tavern_chat_new_entry_item, this)
        this.setBackgroundResource(R.color.white)

        chatEditText.addTextChangedListener(object: TextWatcher {
            override fun afterTextChanged(s: Editable?) {}

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                setSendButtonEnabled(chatEditText.text.isNotEmpty())
            }
        })

52.     sendButton.setOnClickListener { sendButtonPressed() }
    }

Since the action to be executed has to be already defined in the “sendAction” action, the “sendButtonPressed” method verifies that the input field do not have an empty value and invoke the action stored in “sendAction” variable.

Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/social/ChatBarView.kt

    private fun sendButtonPressed() {
        val chatText = chatEditText.text.toString()
        if (chatText.isNotEmpty()) {
            chatEditText.text = null
92.         sendAction?.invoke(chatText)
        }
    }

After defining the view that shows the new message segment of the chat view, we have to see how the previous definition make part of the application. But, when we get into the chat view layout definition, we can see that at line 16 an element of the previous class is defined and at line 17 an id is assigned to manage this element.

Habitica/res/layout/fragment_chat.xml

16. <com.habitrpg.android.habitica.ui.views.social.ChatBarView
17.    android:id="@+id/chatBarView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

An then, using the id assigned in the layout, the “sendAction” property is assigned at line 112 to the “ChatBarView” element.

Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt

112. chatBarView.sendAction = { sendChatMessage(it) }

Based on the previos definition, the “sendChatMessage” method is the one executed when the “sendButton” is clicked. In the following snippet we can see the implementation of that method.

Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/ChatListFragment.kt

     private fun sendChatMessage(chatText: String) {
215.     socialRepository.postGroupChat(groupId, chatText).subscribe(Action1 {
216.         recyclerView?.scrollToPosition(0)
217.     }, RxErrorHandler.handleEmptyError())
     }

To continue, we can search the implementation of the “postGroupChat” in “SocialRepository” class, but “SocialRepository” is an interface, so the following snippet shows the implementation of the “SocialRepository” interface methods. Nevertheless, the implementation call another method with the same name but from “ApiClient” class.

Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/SocialRepositoryImpl.java

    @Override
    public Observable<PostChatMessageResult> postGroupChat(String groupId, HashMap<String, String> messageObject) {
52.     return apiClient.postGroupChat(groupId, messageObject)
                .map(postChatMessageResult -> {
                    if (postChatMessageResult != null) {
                        postChatMessageResult.message.groupId = groupId;
                    }
                    return postChatMessageResult;
                })
                .doOnNext(postChatMessageResult -> {
                    if (postChatMessageResult != null) {
                        localRepository.save(postChatMessageResult.message);
                    }
                });
    }

This method allocated in “ApiClientImpl” is the implementation of the method called in the previous class that belongs to “ApiClient” class that is also an interface. In this implementation we can see that at line 699 the event flow goes to “ApiService postGroupChat” method.

Habitica/src/main/java/com/habitrpg/android/habitica/data/implementation/ApiClientImpl.java

    @Override
    public Observable<PostChatMessageResult> postGroupChat(String groupId, Map<String, String> message) {
        if (groupId == null) {
            return Observable.just(null);
        }
699.    return apiService.postGroupChat(groupId, message).compose(configureApiCallObserver());
    }

Finally, when checking all the code in the “ApiService” class it can be identified the use of an external library in order to execute the request to the different services, in this specific case as it can be seen in the folowwing snippet the method make a post request to “groups/{gid}/chat”. Nevertheless, as we can see in all the method flow created from the beggining there is no network state validation or error handling, so in the case there is no connection to perform the request, this will fail and the message will be lost due to the conectionless state.

Habitica/src/main/java/com/habitrpg/android/habitica/api/ApiService.java

     @POST("groups/{gid}/chat")
208. Observable<HabitResponse<PostChatMessageResult>> postGroupChat(@Path("gid") String groupId, @Body Map<String, String> message);