iT邦幫忙

2024 iThome 鐵人賽

DAY 9
0

前言

本篇文章會設計一個 APP,用來與上一篇文章的後端系統對接,後端系統的部分會修改部分程式碼,改成註冊完帳號後會自動再切換到發送驗證碼的功能,模擬整個註冊系統的效果,預設上會是以使用者的 Email來發送驗證碼,但是這個專案不會做到這個部分,這個部分會直接從後端看到驗證碼來替代從 Email拿到驗證碼的動作。

Gradle

首先為了方便使用 API,我們要添加 RetrofitRxJava3 來幫助我們快速建立API服務,以及本次專案會使用 DataBinding

因此就讓我們先來添加這些設定吧~

android {
    dataBinding {
        enabled = true
    }
}

dependencies {
    //retrofit2 Rxjava3
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")

    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.1.6'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
}

API

image
這些就是本次 APP 中,跟 API 相關的架構,以下就是相關的程式碼設定

ApiClient

import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class ApiClient {
    private final String API_URL = "http://192.168.0.138:9000/api/user/";
    private Retrofit apiClient = null;

    public Retrofit getApiClient() {
        if (apiClient == null) {
            apiClient = new Retrofit.Builder()
                    .baseUrl(API_URL)
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return apiClient;
    }

    public ApiService getApiService() {
        return getApiClient().create(ApiService.class);
    }
}

ApiService

import io.reactivex.rxjava3.core.Observable;
import retrofit2.http.Body;
import retrofit2.http.POST;

public interface ApiService {

    @POST("register")
    Observable<ApiResponse> register(@Body ApiRequest request);

    @POST("auth")
    Observable<ApiResponse> auth(@Body ApiRequest request);
}

ApiRequest

public class ApiRequest {
    private String userName;
    private String email;
    private String password;
    private String verifyCode;

    public ApiRequest(String nickname, String email, String password) {
        this.userName = nickname;
        this.email = email;
        this.password = password;
    }

    public ApiRequest(String nickname, String verifyCode) {
        this.userName = nickname;
        this.verifyCode = verifyCode;
    }

    public String getNickname() {
        return userName;
    }

    public String getEmail() {
        return email;
    }

    public String getPassword() {
        return password;
    }
}

ApiResponse

public class ApiResponse {
    private String message;
    private String status;

    public String getMessage() {
        return message;
    }

    public String getStatus() {
        return status;
    }
}

其他細部設定

要讓我們的 APP 可以打 API,我們還要再設定一些東西

首先我們要新增網路安全配置,在 res > xml 底下建立一個 network_security_config

對 xml 右鍵,新增 XML Resource File,接著照以下的輸入
image

最後就會看到新建了一個 network_security_config.xml的檔案
image

使用 cmd 搭配指令 ipconfig 找到被分配到的 ip 位置,接著像下面這樣輸入

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">192.168.0.138</domain>
    </domain-config>
</network-security-config>

最後到 Manifest 輸入這個,將我們的網路安全配置設定到專案裡面

<application
    android:networkSecurityConfig="@xml/network_security_config"

activity_main.xml

本次的專案畫面全部都是透過 AI生成,Layout的部分由 Android Studio 內建的 Gemini生成,使用到的圖片也都是詢問 AI 生成出的,只要經過微調就可以變成非常有質感的畫面 !

這邊附上我詢問的內容:
幫我設計一個介面,功能要有三個輸入框分別是「 使用者暱稱、 電子信箱、 密碼」, 還要有兩個按鈕, 一個是清除按紐, 另一個是送出按紐, 還要一個checkBox的內文是「確認不是機器人」, 整個layout的背景用一個圖片覆蓋

以及生成結果:
image

MainActivity

這個部分我們將重點的部分展示出來就好,不然一些細部的設定太多會導致文章太長

首先是基本的變數命名,這邊使用 DataBinding快速綁定物件ID,接著建立 ApiClient跟ApiService,最後全部用 Function的方是將功能打包,整體看起來就會很整潔

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private ApiClient apiClient;
    private ApiService apiService;
    private ProgressDialog progressDialog;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        setBinding();

        setApi();

        setButton();

        setCheckBok();
    }

接著設定按鈕的點擊事件,這邊我們先判斷是否有空白的項目,如果有就透過 Toast 提醒用戶資料輸入不齊全,如果都有輸入了就將資料傳送給 registerAccount 的功能裡面,準備要打API向後端註冊帳號

    private void onSubmitButtonClick(View view) {
        if (binding.nicknameEdittext.getText().toString().isEmpty()
                || binding.emailEdittext.getText().toString().isEmpty()
                || binding.passwordEdittext.getText().toString().isEmpty()) {
            Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show();
            return;
        }

        String nickname = binding.nicknameEdittext.getText().toString();
        String email = binding.emailEdittext.getText().toString();
        String password = binding.passwordEdittext.getText().toString();

        registAccount(nickname, email, password);
    }

registAccount

這邊的設計就是一個簡單的打API的功能,將建立好的 Request送到 API服務中,最後使用 Response 接傳回的資料,這邊的設定是如果資料正確並且回傳的狀態值是 1,那就繼續執行下一個步驟「認證帳戶」

    private void registAccount(String nickname, String email, String password) {
        ApiRequest apiRequest = new ApiRequest(nickname, email, password);

        apiService.register(apiRequest)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver<ApiResponse>() {
                    @Override
                    public void onNext(@NonNull ApiResponse response) {
                        if (response != null) {
                            if (response.getStatus().equals("1")){ // success
                                showSendVerifyCodeDialog();
                            } else { // fail
                                Toast.makeText(MainActivity.this, "註冊失敗", Toast.LENGTH_SHORT).show();
                            }
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Toast.makeText(MainActivity.this, "註冊失敗", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

showSendVerifyCodeDialog()

這邊的設計是用戶需要輸入帳戶資訊,以及收到的驗證碼,點選確認按鈕後會再去執行驗證的API

    private void showSendVerifyCodeDialog() {
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
        dialogBuilder.setTitle("驗證")
                .setMessage("請輸入驗證碼,以及帳戶資料");

        LinearLayout layout = new LinearLayout(this);
        layout.setOrientation(LinearLayout.VERTICAL);

        EditText verifyCodeEditText = new EditText(this);
        EditText userNameEditText = new EditText(this);

        verifyCodeEditText.setHint("輸入驗證碼");
        userNameEditText.setHint("輸入帳戶名稱");

        layout.addView(verifyCodeEditText);
        layout.addView(userNameEditText);

        dialogBuilder.setView(layout);

        dialogBuilder.setPositiveButton("確認", (dialog, which) -> {
            String verifyCodeInput = verifyCodeEditText.getText().toString();
            String userName = userNameEditText.getText().toString();

            authAccount(verifyCodeInput, userName);
            dialog.dismiss();
        });

        dialogBuilder.setNegativeButton("取消", (dialog, which) -> {
            Toast.makeText(MainActivity.this, "驗證失敗", Toast.LENGTH_SHORT).show();
            dialog.dismiss();
        });

        dialogBuilder.show();
    }

authAccount

這裡將前一個 AlertDialog 獲取到的驗證碼跟帳戶資訊組合成Request後,送給 API 服務驗證這個帳戶是否沒問題,如果都沒問題就會跳到下個畫面

    private void showProgressDialog(boolean show){
        if (progressDialog == null){
            progressDialog = new ProgressDialog(this);
            progressDialog.setTitle("驗證帳戶中");
            progressDialog.setMessage("請稍後...");
        }
        if (show){
            progressDialog.show();
        }else{
            progressDialog.dismiss();
        }
    }

    private void authAccount(String verifyCodeInput, String userName) {
        showProgressDialog(true);

        ApiRequest apiRequest = new ApiRequest(userName, verifyCodeInput);

        apiService.auth(apiRequest)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DisposableObserver<ApiResponse>() {
                    @Override
                    public void onNext(@NonNull ApiResponse response) {
                        if (response != null) {
                            if (response.getStatus().equals("1")){ // success
                                Toast.makeText(MainActivity.this, "註冊成功", Toast.LENGTH_SHORT).show();
                                showProgressDialog(false);

                                Intent intent = new Intent(MainActivity.this, MainActivity2.class);
                                startActivity(intent);
                            } else { // fail
                                Toast.makeText(MainActivity.this, "驗證失敗", Toast.LENGTH_SHORT).show();
                                showProgressDialog(false);
                            }
                        }
                    }

                    @Override
                    public void onError(@NonNull Throwable e) {
                        Toast.makeText(MainActivity.this, "驗證失敗", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

後端

最後別忘了在後端的部分要修改一點邏輯
我們要修改的只有 register 的部分,我們將原本註冊成功會回復註冊成功等等的訊息改成直接再往下發送驗證碼

    public UserResponse register(UserRequest request) {
        var response = UserResponse.builder();
        try {
            String userName = request.userName();

            if (userRepository.findByName(userName).isEmpty()) {
                var user = UserEntity.builder()
                        .name(userName)
                        .email(request.email())
                        .password(request.password())
                        .build();

                userRepository.save(user);

                return sendVerifyCode(request);
            } else {
                response.status("0");
                response.message("The account has been registered.");
            }

            return response.build();
        } catch (Exception e) {
            response.status("0");
            response.message("There is something went wrong : " + e.getMessage());

            return response.build();
        }
    }

還有我們可以在發送驗證碼的地方接一個log來獲取驗證碼的資料,當然這邊也可以直接從資料庫查看就好

            if (userRepository.findByName(userName).isPresent() &&
                    !request.email().isEmpty() ) {
                String verifyCode = generateVerifyCode();

                userRepository.updateVerifyCodeByEmail(verifyCode, request.email());

                response.status("1");
                response.message("Success");
                response.verifyCode(verifyCode);

                log.info("驗證碼 : " + verifyCode);

使用 log 要記得加上 @Slf4j

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

成果展示

image

image

image

image

image

image

總結

本篇文章簡單製作了一個 APP 與前篇文章製作的後端系統對接,透過這個範例我們可以了解一個註冊系統的運作方式,並且透過內建的 Gemini輔助開發整個APP,總共耗時 3小時整


上一篇
[DAY 08] 建立第一個簡單的後端系統 2
下一篇
[DAY 10] JWT 介紹
系列文
智慧語義互動平台:基於Spring和Semantic Kernel的Android應用創新20
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言