Repository: fossasia/pslab-android
Pull request #2642: fix: ESP01 connection
Author: login: AsCress
Labels: name: Fix
description: Solution to an existing issue in app
Created: 2025-03-10T15:32:13Z | Merged: 2025-03-10T17:27:44Z

Fixes the connection with ESP01 chip.

## Summary by Sourcery

Implements connection to ESP01 chip via WiFi, including background task to handle socket connection and UI updates based on connection status.


Diff:
diff --git a/app/src/main/java/io/pslab/fragment/ESPFragment.java b/app/src/main/java/io/pslab/fragment/ESPFragment.java
deleted file mode 100644
index 78bec4286..000000000
--- a/app/src/main/java/io/pslab/fragment/ESPFragment.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package io.pslab.fragment;
-
-import android.app.Activity;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
-
-import com.google.android.material.snackbar.Snackbar;
-
-
-import io.pslab.R;
-import io.pslab.communication.SocketClient;
-import io.pslab.others.CustomSnackBar;
-import io.pslab.others.ScienceLabCommon;
-
-public class ESPFragment extends DialogFragment {
-    private static final String TAG = ESPFragment.class.getSimpleName();
-
-    private String espIPAddress = "";
-    private ProgressBar espConnectProgressBar;
-    private Button espConnectBtn;
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Nullable
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
-        View rootView = inflater.inflate(R.layout.fragment_esp, container, false);
-        EditText espIPEditText = rootView.findViewById(R.id.esp_ip_edit_text);
-        espConnectBtn = rootView.findViewById(R.id.esp_connect_btn);
-        espConnectProgressBar = rootView.findViewById(R.id.esp_connect_progressbar);
-        espConnectBtn.setOnClickListener(v -> {
-            espIPAddress = espIPEditText.getText().toString();
-            espConnectBtn.onEditorAction(EditorInfo.IME_ACTION_DONE);
-            Activity activity;
-            if (espIPAddress.isEmpty() && ((activity = getActivity()) != null)) {
-                CustomSnackBar.showSnackBar(activity.findViewById(android.R.id.content),
-                        getString(R.string.incorrect_IP_address_message), null, null, Snackbar.LENGTH_SHORT);
-            } else {
-                new ESPTask().execute();
-            }
-        });
-        espIPEditText.setOnEditorActionListener((v, actionId, event) -> {
-            if (actionId == EditorInfo.IME_ACTION_DONE) {
-                espConnectBtn.performClick();
-                return true;
-            }
-            return false;
-        });
-        return rootView;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
-        params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
-        params.width = ViewGroup.LayoutParams.MATCH_PARENT;
-        getDialog().getWindow().setAttributes(params);
-    }
-
-    private class ESPTask extends AsyncTask<Void, Void, Boolean> {
-
-        @Override
-        protected void onPreExecute() {
-            espConnectBtn.setVisibility(View.GONE);
-            espConnectProgressBar.setVisibility(View.VISIBLE);
-        }
-
-        @Override
-        protected Boolean doInBackground(Void... voids) {
-            try {
-                SocketClient socketClient = SocketClient.getInstance();
-                socketClient.openConnection(espIPAddress, 80);
-                if (socketClient.isConnected()) {
-                    ScienceLabCommon.setIsWifiConnected(true);
-                    ScienceLabCommon.setEspBaseIP(espIPAddress);
-                    return true;
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            return false;
-        }
-
-        @Override
-        protected void onPostExecute(Boolean result) {
-            espConnectProgressBar.setVisibility(View.GONE);
-            espConnectBtn.setVisibility(View.VISIBLE);
-            Activity activity;
-            if (!result && ((activity = getActivity()) != null)) {
-                CustomSnackBar.showSnackBar(activity.findViewById(android.R.id.content),
-                        getString(R.string.incorrect_IP_address_message), null, null, Snackbar.LENGTH_SHORT);
-            } else {
-                Log.v("ESPFragment", "ESP Connection Successful");
-                ScienceLabCommon.getInstance().openDevice(null);
-                ScienceLabCommon.isWifiConnected = true;
-                getParentFragmentManager().beginTransaction().remove(ESPFragment.this).commitAllowingStateLoss();
-                getParentFragmentManager().beginTransaction().replace(R.id.frame, HomeFragment.newInstance(true, false)).commitAllowingStateLoss();
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/pslab/fragment/HomeFragment.java b/app/src/main/java/io/pslab/fragment/HomeFragment.java
index c78888fb8..a1d69f087 100644
--- a/app/src/main/java/io/pslab/fragment/HomeFragment.java
+++ b/app/src/main/java/io/pslab/fragment/HomeFragment.java
@@ -2,9 +2,12 @@
 
 import static io.pslab.others.ScienceLabCommon.scienceLab;
 
+import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.Paint;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -32,6 +35,7 @@
 import butterknife.Unbinder;
 import io.pslab.R;
 import io.pslab.activity.MainActivity;
+import io.pslab.communication.SocketClient;
 import io.pslab.others.CustomSnackBar;
 import io.pslab.others.InitializationVariable;
 import io.pslab.others.PSLabPermission;
@@ -105,8 +109,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
             }
             imgViewDeviceStatus.setImageResource(R.drawable.icons8_usb_connected_100);
             tvDeviceStatus.setText(getString(R.string.device_connected_successfully));
-        }
-        else if (!deviceFound && deviceConnected) {
+        } else if (!deviceFound && deviceConnected) {
             tvConnectMsg.setVisibility(View.GONE);
             try {
                 tvVersion.setText(scienceLab.getVersion());
@@ -119,8 +122,7 @@ else if (!deviceFound && deviceConnected) {
             requireActivity().invalidateOptionsMenu();
             CustomSnackBar.showSnackBar(requireActivity().findViewById(android.R.id.content),
                     getString(R.string.device_connected_successfully), null, null, Snackbar.LENGTH_SHORT);
-        }
-        else {
+        } else {
             imgViewDeviceStatus.setImageResource(R.drawable.icons_usb_disconnected_100);
             tvDeviceStatus.setText(getString(R.string.device_not_found));
         }
@@ -207,14 +209,50 @@ public void onClick(View v) {
         wifiButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                ESPFragment espFragment = new ESPFragment();
-                espFragment.show(getActivity().getSupportFragmentManager(), "wifi");
-                espFragment.setCancelable(true);
+                new ESPTask().execute();
             }
         });
         return view;
     }
 
+    private class ESPTask extends AsyncTask<Void, Void, Boolean> {
+
+        @Override
+        protected void onPreExecute() {
+            /**/
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            try {
+                SocketClient socketClient = SocketClient.getInstance();
+                socketClient.openConnection("192.168.4.1", 80);
+                if (socketClient.isConnected()) {
+                    ScienceLabCommon.setIsWifiConnected(true);
+                    ScienceLabCommon.setEspBaseIP("192.168.4.1");
+                    return true;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return false;
+        }
+
+        @Override
+        protected void onPostExecute(Boolean result) {
+            Activity activity;
+            if (!result && ((activity = getActivity()) != null)) {
+                CustomSnackBar.showSnackBar(activity.findViewById(android.R.id.content),
+                        getString(R.string.incorrect_IP_address_message), null, null, Snackbar.LENGTH_SHORT);
+            } else {
+                Log.v("ESPFragment", "ESP Connection Successful");
+                ScienceLabCommon.getInstance().openDevice(null);
+                ScienceLabCommon.isWifiConnected = true;
+                getParentFragmentManager().beginTransaction().replace(R.id.frame, HomeFragment.newInstance(true, false)).commitAllowingStateLoss();
+            }
+        }
+    }
+
     public void hideWebView() {
         webView.setVisibility(View.GONE);
         svHomeContent.setVisibility(View.VISIBLE);


Reviews:
user: login: sourcery-ai[bot]
body: Hey @AsCress - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider using a more descriptive name than `ESPTask` for the `AsyncTask`.
- The `onPreExecute` method in `ESPTask` is empty; either add functionality or remove it.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟡 **General issues**: 1 issue found
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T15:33:17Z
user: login: marcnause
body: Successfully tested on my device (Fairphone 4, Android 13) and with the PSLab v6 wit the 3.1.0 firmware.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T17:18:46Z

Review comments:
diff_hunk: @@ -207,14 +209,50 @@ public void onClick(View v) {
         wifiButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                ESPFragment espFragment = new ESPFragment();
-                espFragment.show(getActivity().getSupportFragmentManager(), "wifi");
-                espFragment.setCancelable(true);
+                new ESPTask().execute();
             }
         });
         return view;
     }
 
+    private class ESPTask extends AsyncTask<Void, Void, Boolean> {
+
+        @Override
+        protected void onPreExecute() {
+            /**/
+        }
+
+        @Override
+        protected Boolean doInBackground(Void... voids) {
+            try {
+                SocketClient socketClient = SocketClient.getInstance();
+                socketClient.openConnection("192.168.4.1", 80);
+                if (socketClient.isConnected()) {
+                    ScienceLabCommon.setIsWifiConnected(true);
+                    ScienceLabCommon.setEspBaseIP("192.168.4.1");
+                    return true;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            return false;
+        }
+
path: app/src/main/java/io/pslab/fragment/HomeFragment.java
user: login: sourcery-ai[bot]
body: **suggestion:** Avoid duplicate updates to the Wifi connection flag.

Since the flag is already updated via ScienceLabCommon.setIsWifiConnected(true) in doInBackground, reassigning ScienceLabCommon.isWifiConnected in onPostExecute might be redundant. Consider consolidating the state update to a single spot.

Suggested implementation:

```java
        @Override
        protected void onPostExecute(Boolean result) {
            // Removed duplicate update of the Wifi connection flag; already being set in doInBackground.
            // ... any additional processing
        }

```

If there are any other parts of the code that rely on the updated Wifi connection flag within onPostExecute, ensure that their behavior is not impacted by this change.
created_at: 2025-03-10T15:33:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -207,14 +209,50 @@ public void onClick(View v) {
         wifiButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                ESPFragment espFragment = new ESPFragment();
-                espFragment.show(getActivity().getSupportFragmentManager(), "wifi");
-                espFragment.setCancelable(true);
+                new ESPTask().execute();
path: app/src/main/java/io/pslab/fragment/HomeFragment.java
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider decoupling the networking logic from the fragment into a dedicated utility class to reduce cognitive load and separate UI code from background tasks, which simplifies the AsyncTask and isolates networking concerns for easier maintenance and future adaptations..

Consider decoupling the networking logic from the fragment. You can extract the ESP connection logic into a dedicated utility class. This reduces the cognitive load in the fragment and keeps UI code separate from background tasks. For example:

1. **Create a networking utility:**

```java
public class ESPConnector {
    public static boolean connect(String ip, int port) {
        try {
            SocketClient socketClient = SocketClient.getInstance();
            socketClient.openConnection(ip, port);
            if (socketClient.isConnected()) {
                ScienceLabCommon.setIsWifiConnected(true);
                ScienceLabCommon.setEspBaseIP(ip);
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
```

2. **Simplify the AsyncTask in your fragment:**

```java
private class ESPTask extends AsyncTask<Void, Void, Boolean> {
    @Override
    protected void onPreExecute() {
        // Optionally show a progress indicator
    }

    @Override
    protected Boolean doInBackground(Void... voids) {
        return ESPConnector.connect("192.168.4.1", 80);
    }

    @Override
    protected void onPostExecute(Boolean result) {
        Activity activity = getActivity();
        if (!result && activity != null) {
            CustomSnackBar.showSnackBar(activity.findViewById(android.R.id.content),
                    getString(R.string.incorrect_IP_address_message), null, null, Snackbar.LENGTH_SHORT);
        } else {
            Log.v("ESPFragment", "ESP Connection Successful");
            ScienceLabCommon.getInstance().openDevice(null);
            ScienceLabCommon.isWifiConnected = true;
            getParentFragmentManager().beginTransaction()
                    .replace(R.id.frame, HomeFragment.newInstance(true, false))
                    .commitAllowingStateLoss();
        }
    }
}
```

This refactoring isolates the networking concerns and makes further adaptations (like moving to an Executor or more modern async patterns) easier to manage.
created_at: 2025-03-10T15:33:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-10T15:32:19Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request introduces an asynchronous task to handle the connection with the ESP01 chip. It establishes a socket connection in the background and updates the UI based on the connection status. The previous implementation using `ESPFragment` has been removed.

#### Sequence diagram for ESP01 connection

```mermaid
sequenceDiagram
  participant HomeFragment
  participant ESPTask
  participant SocketClient
  participant ScienceLabCommon

  HomeFragment->>ESPTask: Execute ESPTask
  ESPTask->>SocketClient: getInstance()
  ESPTask->>SocketClient: openConnection("192.168.4.1", 80)
  alt SocketClient is connected
    SocketClient-->>ESPTask: true
    ESPTask->>ScienceLabCommon: setIsWifiConnected(true)
    ESPTask->>ScienceLabCommon: setEspBaseIP("192.168.4.1")
    ESPTask-->>HomeFragment: true
    HomeFragment->>ScienceLabCommon: openDevice(null)
    HomeFragment->>ScienceLabCommon: isWifiConnected = true
    HomeFragment->>HomeFragment: replace fragment
  else SocketClient is not connected
    SocketClient-->>ESPTask: false
    ESPTask-->>HomeFragment: false
    HomeFragment->>HomeFragment: Show error message
  end
```

#### Updated class diagram for HomeFragment

```mermaid
classDiagram
    class HomeFragment {
        -WebView webView
        -ScrollView svHomeContent
        -ImageView imgViewDeviceStatus
        -TextView tvDeviceStatus
        -TextView tvVersion
        -TextView tvConnectMsg
        -Button wifiButton
        +View onCreateView()
        +void hideWebView()
        +void onClick(View v)
    }

    class ESPTask {
        -String ipAddress
        -int port
        +Boolean doInBackground(Void... voids)
        +void onPostExecute(Boolean result)
    }

    HomeFragment -- ESPTask : Uses
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Implements a background task to handle ESP01 connection and updates the UI based on the connection status. | <ul><li>Created an `ESPTask` inner class extending `AsyncTask` to handle the ESP01 connection in the background.</li><li>Implemented `doInBackground` to establish a socket connection with the ESP01 chip at IP address 192.168.4.1 on port 80.</li><li>Set `ScienceLabCommon.isWifiConnected` and `ScienceLabCommon.setEspBaseIP` if the connection is successful.</li><li>Implemented `onPostExecute` to display a success message or an error message using `CustomSnackBar` based on the connection result.</li><li>Reloads the `HomeFragment` upon successful connection.</li><li>Removes the `ESPFragment` class.</li></ul> | `app/src/main/java/io/pslab/fragment/HomeFragment.java`<br/>`app/src/main/java/io/pslab/fragment/ESPFragment.java` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-10T15:38:32Z
body: Build successful. APKs to test: https://github.com/fossasia/pslab-android/actions/runs/13768567953/artifacts/2723354957
author_association: CONTRIBUTOR