Lập trình Android – Bài 8: Intent – Chuyển đổi giữa các màn hình

Qua 7 bài cơ bản trước đó, nhất định các bạn đã nắm tương đối ổn về cách thiết kế giao diện cũng như bắt sự kiện button. Và mình cũng tin rằng nếu bạn nào theo dõi loạt bài của mình mà có ý định xa hơn – nghĩa là ham học hơn những người khác thì rất tò con nhà bà mò là làm thế nào có thể click button mà có thể mở một màn hình khác. Bài hôm nay chúng ta sẽ làm điều đó thông qua một ứng dụng đơn giản – Đăng ký đăng nhập.

[qads]

Nguyên lý sơ qua của ứng dụng này như sau: Bạn sẽ phải đăng nhập vào một hệ thống bằng cách điền tên đăng nhập và mật khẩu. Nếu đúng thì chuyển sang màn hình mới để bắt đầu sử dụng hệ thống. Nếu người dùng chưa có tài khoản thì có thể đăng ký tài khoản, sau khi đăng ký thì trở về màn hình đăng nhập và tự động điền tên vào ô tên đăng nhập. Như vậy ta có sơ đồ ứng dụng như sau:

android-login-form-1

Rồi, bây giờ các bạn mở Android Studio lên và tạo một Project mới. Lưu ý một chút là tại thời điểm hiện tại, Android Studio có cho phép chúng ta tạo một Login Activity, tuy nhiên mình không sử dụng nó vì nó sẽ tự động sinh ra rất nhiều code, để giải thích hết đống code đó cho các bạn thì mất rất nhiều thời gian và sẽ không tập trung vào bài này được. Do vậy các bạn hãy tự nghiên cứu code của họ nhé. Ở đây mình vẫn tạo từ Empty Activity.

Khi tạo Project mình đặt tên Activity là LoginActivity, file xml là activity_login, Title là Login. Các bạn tạm thời chưa cần code java nhé. Giờ chúng ta hãy đi tạo nốt 2 activity còn lại đã.
Trước tiên tạo Activity Register (đăng ký). Các bạn click chuột phải vào package, chọn new -> Activity -> Empty Activty

android-create-activity

Tiếp theo các bạn đặt tên Activity và tên file giao diện, thường sẽ tự động đặt thên theo tên Activity.

android-create-activity-1

Lưu ý các bạn không tích vào ô Launcher Activity nhé. Nếu tích vào thì Activity này sẽ mở lên khi các bạn mở app.
Tương tự, chúng ta tạo nốt Activity Main – Giao diện khi mà Login thành công.

Giờ bắt đầu thiết kế giao diện và code Java.

Trước tiên các bạn đặt các biến trong file /res/values/string.xml

<resources>
    <string name="app_name">TUT8Intent</string>

    <!-- Strings related to login -->
    <string name="login">Login</string>
    <string name="user_name">User name</string>
    <string name="password">Password</string>
    <string name="confirm_password">Confirm Password</string>

    <string name="register">Register</string>

    <string name="action_sign_in_short">Sign in</string>
    <string name="error_field_required">This field is required</string>
    <string name="error_password_not_match">Password not match</string>

    <string name="login_success">Hi, {0}. You login with password is {1}</string>
</resources>

Chúng ta thêm thư viện design để dùng TextInputLayout kết hợp với EditText bằng cách mở file build.gradle và sửa dependencies thành:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.0.0'
    compile 'com.android.support:design:24.0.0'
}

Trong tất cả các giao diện trên, dễ thấy chúng ta có thể dùng LinearLayout cho thuận tiện. Dưới đây là code giao diện cho LoginActivity.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp">

    <!-- Login progress -->
    <ProgressBar
        android:id="@+id/login_progress"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:visibility="gone" />

    <ScrollView
        android:id="@+id/login_form"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/editUserName"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/user_name"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/editPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/password"
                    android:inputType="textPassword"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/btnLogin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="16dp"
                android:text="@string/login" />

        </LinearLayout>
    </ScrollView>
</LinearLayout>

Phần này mình sẽ không giải thích gì thêm ngoại trừ đối tượng ProgressBar Đây là một đối tượng hiển thị dạng chờ, nó sẽ quay tròn (hoặc ngang, nhưng ở đây là tròn) để cho biết người dùng là hãy đợi chúng tôi đang xử lý công việc.
Thuộc tính android:visibility với giá trị gone sẽ làm nó ẩn đi, khi nào các bạn muốn nó hiện lên thì đặt giá trị cho nó là visible

Code java xử lý Login Activity như sau:

package com.nguyenvanquan7826.tut8intent;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {

    public static final String KEY_USER_TO_MAIN = "KEY_USER_TO_MAIN";
    public static final String KEY_PASSWORD_TO_MAIN = "KEY_PASSWORD_TO_MAIN";

    public static final String KEY_USER_FROM_REGISTER = "KEY_USER_FROM_REGISTER";

    public static final int REQUEST_CODE_REGISTER = 1;

    private Context context;

    private EditText editUserName;
    private EditText editPassword;

    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        context = this;

        connectView();
    }

    private void connectView() {
        editUserName = (EditText) findViewById(R.id.editUserName);
        editPassword = (EditText) findViewById(R.id.editPassword);

        progressBar = (ProgressBar) findViewById(R.id.progressLogin);

        findViewById(R.id.btnLogin).setOnClickListener(this);
        findViewById(R.id.btnRegister).setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.btnLogin:
                login();
                break;
            case R.id.btnRegister:
                register();
                break;
        }
    }

    private void login() {
        boolean error = false;

        showProgress(true);
        // when process we must have sometime

        // get data
        String userName = editUserName.getText().toString().trim();
        String password = editPassword.getText().toString().trim();

        // password empty
        if (TextUtils.isEmpty(password)) {
            editPassword.requestFocus();
            editPassword.setError(context.getResources().getString(R.string.error_field_required));
            error = true;
        }

        // username empty
        if (TextUtils.isEmpty(userName)) {
            editUserName.requestFocus();
            editUserName.setError(context.getResources().getString(R.string.error_field_required));
            error = true;
        }

        // all data is ok
        showProgress(false);

        if (!error) {
            // create intent to show Main Activity
            Intent intent = new Intent(context, MainActivity.class);

            // send data if need
            intent.putExtra(KEY_USER_TO_MAIN, userName);
            intent.putExtra(KEY_PASSWORD_TO_MAIN, password);

            // start Main Activity
            startActivity(intent);
        }
    }

    private void register() {
        Intent intent = new Intent(context, RegisterActivity.class);
        startActivityForResult(intent, REQUEST_CODE_REGISTER);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == REQUEST_CODE_REGISTER && resultCode == Activity.RESULT_OK) {
            String userName = data.getStringExtra(KEY_USER_FROM_REGISTER);
            editUserName.setText(userName);
            editPassword.requestFocus();
        }
    }

    private void showProgress(boolean isShow) {
        progressBar.setVisibility(isShow ? View.VISIBLE : View.GONE);
        findViewById(R.id.login_form).setVisibility(!isShow ? View.VISIBLE : View.GONE);
    }
}

Trong code này các bạn cần lưu ý:
– Tại hàm login, chúng ta có gọi 2 lần showProgress(true); showProgress(false); tức là khi ban đầu xử lý sẽ mất chút thời gian và cho progress hiện lên, rồi xử lý xong thì ẩn đi. Nhưng vì công việc của chúng ta quá nhanh nên bạn sẽ không nhìn thấy progress hiện lên.

  • Sau khi mọi dữ liệu đều ổn thì bắt đầu dùng Intent để gọi MainActivity ra. Tại đây các bạn chú ý:
    // create intent to show Main Activity
    Intent intent = new Intent(context, MainActivity.class);
    
    // send data if need
    intent.putExtra(KEY_USER_TO_MAIN, userName);
    intent.putExtra(KEY_PASSWORD_TO_MAIN, password);
    
    // start Main Activity
    startActivity(intent);
    

    +/ Dòng thứ nhất chính là cách mà chúng ta tạo Intent để chuyển từ Activity này sang Activity khác. Trong đó context chính là ngữ cảnh – là Activity hiện tại, MainActivity.class chính là Activity chúng ta cần chuyển đến.
    +/ Dòng thứ 2,3 chúng ta dùng intent để đưa một số dữ liệu sang, ở đây là đưa userName và password sang MainActivity thông qua 1 từ khóa là KEY_USER_TO_MAIN và KEY_PASSWORD_TO_MAIN, nó là 2 hằng số, nó cũng chính là key – khóa để sang bên kia chúng ta có thể nhận chính xác đâu là userName, đâu là password.
    +/ Dòng cuối cùng là để bắt đầu chuyển sang Activity mới.

  • Bạn thấy một điểm khác biệt nữa là mình có đặt Error cho EditText UserName và EditText Password. Khi báo lỗi chúng ta có giao diện đẹp như sau:

error-for-edittext

  • Khi gọi hàm register, mình tạo Intent tương tự như trên, tuy nhiên các bạn nhớ lại quy trình là sau khi đăng ký xong, chúng ta cần điền tên vừa đăng ký vào EditText UserName, vì vậy mình phải gọi làm sao để sau khi kết thúc RegisterActivity thì sẽ nhận kết quả trả về là UserName đó thông qua lệnh:
    startActivityForResult(intent, REQUEST_CODE_REGISTER);
    => startActivity – bắt đầu activity, ForResult là cho phép trả lại kết quả. Tại đây, REQUEST_CODE_REGISTER là một số nguyên, nó cũng là khóa để chúng ta nhận biết xem kết quả trả về được trả từ Activity nào, vì trong thực tế, 1 Activty có thể gọi nhiều Activity và nhiều Activity đó cũng có thể trả về kết quả cho Activty gốc.

  • Để nhận được dữ liệu trả về tử Register, chúng ta phải viết đè hàm onActivityResult – khi kết quả trả về Activity. Tại đây:
    requestCode chính là mã phân biệt khi chúng ta gọi Activity nào, để nhận đúng kết quả của RegisterAcitivty, chúng ta cần kiểm tra nó với REQUEST_CODE_REGISTER xem khớp không.
    resultCode là mã xác nhận xem RegisterAcitivty có chấp nhận trả về kết quả không, nếu là RESULT_OK thì được.
    Tiếp đó chúng ta mới nhận kết quả thông qua Intent data.

Đến đây các bạn hoàn toàn có thể chạy ứng dụng, nhưng ở MainActivity và RegisterActivity chúng ta chưa có gì. Vậy nên giờ lại tiếp tục.

Code giao diện activity main:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.nguyenvanquan7826.tut8intent.MainActivity">

    <TextView
        android:id="@+id/tvMain"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

Code java activity main:

package com.nguyenvanquan7826.tut8intent;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Context context;

    private TextView tvMain;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context = this;

        connectView();
        getData();
    }

    private void connectView() {
        tvMain = (TextView) findViewById(R.id.tvMain);
    }

    private void getData() {
        String userName = getIntent().getStringExtra(LoginActivity.KEY_USER_TO_MAIN);
        String password = getIntent().getStringExtra(LoginActivity.KEY_PASSWORD_TO_MAIN);

        String helloText = context.getResources().getString(R.string.login_success);
        helloText = completeText(helloText, new String[]{userName, password});
        tvMain.setText(helloText);
    }

    private String completeText(String source, String[] items) {
        for (int i = 0; i < items.length; i++) {
            source = source.replace("{" + i + "}", items[i]);
        }
        return source;
    }
}

Ở trên, chúng ta lấy dữ liệu bằng lệnh getIntent().getStringExtra, lấy giá trị nào thì phải truyền KEY tương ứng và chính xác giống như khi chúng ta đã gửi. Giờ các bạn lưu ý tại sao mình lại đặt KEY_USER_TO_MAIN và KEY_PASSWORD_TO_MAIN là các hằng số? Vì sợ nhầm lẫn, ở nhiều nơi, nếu các bạn truyền thằng chuỗi vào làm KEY thì bạn phải nhớ nó và viết chính xác nó ở nhiều nơi. Khi chúng ta đặt hằng thì cứ gọi tên hằng là ok, không phải lo nghĩ gì về chuyện nhầm nhọt.

Tiếp tục với Activity cuối là Register nhé. Nó tương tự như Login nên các bạn chỉ quan tâm đến cách gửi dữ liệu trở về cho LoginActivity thôi nhé.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="16dp"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:paddingTop="16dp">

    <!-- Login progress -->
    <ProgressBar
        android:id="@+id/progressLogin"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:visibility="gone" />

    <ScrollView
        android:id="@+id/login_form"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/editUserName"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/user_name"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/editPassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/password"
                    android:inputType="textPassword"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <android.support.design.widget.TextInputLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <EditText
                    android:id="@+id/editRePassword"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:hint="@string/confirm_password"
                    android:inputType="textPassword"
                    android:singleLine="true" />

            </android.support.design.widget.TextInputLayout>

            <Button
                android:id="@+id/btnRegister"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginTop="16dp"
                android:text="@string/register" />

        </LinearLayout>
    </ScrollView>
</LinearLayout>

Code java của RegisterActivity

package com.nguyenvanquan7826.tut8intent;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.ProgressBar;

public class RegisterActivity extends AppCompatActivity implements View.OnClickListener {
    private Context context;

    private EditText editUserName;
    private EditText editPassword;
    private EditText editRePassword;

    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_register);

        context = this;

        connectView();
    }

    private void connectView() {
        editUserName = (EditText) findViewById(R.id.editUserName);
        editPassword = (EditText) findViewById(R.id.editPassword);
        editRePassword = (EditText) findViewById(R.id.editRePassword);

        progressBar = (ProgressBar) findViewById(R.id.progressLogin);

        findViewById(R.id.btnRegister).setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        int id = view.getId();
        switch (id) {
            case R.id.btnRegister:
                register();
                break;
        }
    }

    private void register() {
        boolean error = false;

        showProgress(true);
        // when process we must have sometime

        // get data
        String userName = editUserName.getText().toString().trim();
        String password = editPassword.getText().toString().trim();
        String rePassword = editRePassword.getText().toString().trim();

        // password empty
        if (TextUtils.isEmpty(rePassword)) {
            editRePassword.requestFocus();
            editRePassword.setError(context.getResources().getString(R.string.error_field_required));
            error = true;
        }

        // password empty
        if (TextUtils.isEmpty(password)) {
            editPassword.requestFocus();
            editPassword.setError(context.getResources().getString(R.string.error_field_required));
            error = true;
        }

        // username empty
        if (TextUtils.isEmpty(userName)) {
            editUserName.requestFocus();
            editUserName.setError(context.getResources().getString(R.string.error_field_required));
            error = true;
        }

        if (!password.equals(rePassword)) {
            editRePassword.requestFocus();
            editRePassword.setError(context.getResources().getString(R.string.error_password_not_match));
            error = true;
        }

        // all data is ok
        showProgress(false);

        if (!error) {
            // create intent to send data back Login Activity
            Intent intent = new Intent();

            // send data
            intent.putExtra(LoginActivity.KEY_USER_FROM_REGISTER, userName);

            setResult(RESULT_OK, intent);
            finish();
        }
    }

    private void showProgress(boolean isShow) {
        progressBar.setVisibility(isShow ? View.VISIBLE : View.GONE);
        findViewById(R.id.login_form).setVisibility(!isShow ? View.VISIBLE : View.GONE);
    }
}