Đăng nhập và đăng ký trên Android là các tác vụ rất thông dụng. Ta thường dùng chúng khi muốn mở tài khoản và thu thập thông tin người dùng.
Hướng dẫn này sẽ thiết lập một web server ở local với cơ sở dữ liệu MySQL. Sau đó ta tạo ứng dụng Android có cho đăng nhập và đăng ký, sử dụng script PHP để kết nối với database đó.
Đăng nhập và đăng ký trên Android
Bước đầu tiên là tạo web server ở backend. Bài này sử dụng Mac OS X và XAMPP để nhanh chóng thiết lập một web server Apache ở local và một databasse MySQL.
Thiết lập server XAMPP
XAMPP (hoặc WAMP) là một bộ cài đặt siêu tiện lợi one-click để tạo ra đầy đủ một môi trường phát triển ứng dụng web có PHP và MySQL (mà chúng ta sẽ kết nối với ứng dụng Android của mình).
Tải xuống và cài đặt XAMPP từ trang chủ của Apache Friends. Chạy ứng dụng XAMPP sau khi cài đặt xong và ta sẽ thấy màn hình bên dưới.
Bạn có thể kiểm tra server của mình bằng cách mở https://localhost trong trình duyệt. Màn hình sau sẽ xuất hiện.
Ngoài ra, bạn có thể kiểm tra phpMyAdmin bằng cách mở https://localhost/phpmyadmin. Hãy xem nó hiển thị gì!
Nếu bạn gặp phải màn hình như thế này, có vẻ như server MySQL không đã khởi chạy đúng cách. Chuyển đến tab “Manage Servers” trong ứng dụng XAMPP và nhấp vào “Restart All”. Các server sẽ chạy bình thường như hình bên dưới.
Bây giờ, hãy kiểm tra lại phpMyAdmin trên localhost và bạn sẽ thấy một màn hình tương tự như thế này.
Giờ hãy thử nghiệm một script PHP mẫu. Tạo một file mới tên là test.php
và thêm các dòng này vào đó.
<?php
echo "Hello, World";
?>
Trong đoạn code trên:
?php
là tag mở đầu cho bất kỳ script PHP nào.?>
là thẻ đóng, tương tự như dấu ngoặc đóng trong Java.
Lưu ý: Bạn không cần biết PHP để làm theo bài hướng dẫn này.
Nếu bạn đang sử dụng MAC, hãy vào Applications -> Xampp -> htdocs. Tạo một thư mục mới ở đây (ví dụ test_android), rồi sao chép file test.php đã tạo trước đó vào thư mục này.
Bây giờ, mở URL https://localhost/test_android/test.php. Bạn sẽ thấy một màn hình như sau:
Thiết lập cơ sở dữ liệu MySQL
Mở phpMyAdmin bằng cách truy cập https://localhost/phpmyadmin. Bây giờ, chọn tab “Databases” ở phía trên bên trái. Đặt một tên bất kỳ rồi nhấn tạo nó. Cơ sở dữ liệu trống mới tạo sẽ hiển thị ở thanh bên trái.
Hãy tạo một bảng users trong database vừa tạo. Chạy query sau trong console:
CREATE TABLE `firstDB`.`users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR( 20 ) NOT NULL ,
`password` VARCHAR( 20 ) NOT NULL
)
Nếu bảng được tạo thành công, bạn sẽ thấy một màn hình tương tự như sau:
Kết nối PHP với cơ sở dữ liệu MySQL
Để kết nối một script PHP với cơ sở dữ liệu MySQL, ta cần cung cấp ba thông số. Dưới đây là các tham số đầu vào và giá trị mặc định của chúng cho một server XAMPP:
- Tên host: localhost
- Tên người dùng MySQL: root
- Mật khẩu MySQL: Để trống. “”
Hãy tạo một script test-connect.php
và thêm nó vào thư mục htdocs->test-android
.
<?php
$host="localhost";
$user="root";
$password="";
$con=mysql_connect($host,$user,$password);
if($con) {
echo '<h1>Connected to MySQL</h1>';
} else {
echo '<h1>MySQL Server is not connected</h1>';
}
?>
mysql_connect()
là một hàm tích hợp sẵn của PHP để kết nối với cơ sở dữ liệu MySQL với các tham số được liệt kê ở trên. Thử chạy https://localhost/test_android/test-connect.php
để xem kết quả. Nếu không kết nối được, hãy thử khởi động lại các server XAMPP.
Ứng dụng đăng nhập và đăng ký trên Android
Sau khi đã nói về việc thiết lập cơ bản PHP và MySQL, ta sẽ chuyển sang phần ứng dụng đăng nhập Android. Ở đây ta sẽ phát triển một ứng dụng đăng nhập/đăng ký. Để giữ cho nó ngắn gọn và đơn giản, chúng ta sẽ kiểm tra xem tên người dùng và email có là duy nhất không trong quá trình đăng ký.
Trước khi đi vào logic của ứng dụng, ta sẽ thay đổi các script PHP và MySQL. Đầu tiên, hãy xóa (DROP) bảng users
và tạo một bảng mới phù hợp với ứng dụng trên.
CREATE TABLE IF NOT EXISTS `firstDB`.`users` (
`id` int(20) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` varchar(70) NOT NULL,
`password` varchar(40) NOT NULL,
`email` varchar(50) NOT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime DEFAULT NULL
)
Dưới đây là các script PHP mà bạn có thể sao chép và paste vào thư mục htdocs->test_android
.
config.php
<?php
define("DB_HOST", "localhost");
define("DB_USER", "root");
define("DB_PASSWORD", "");
define("DB_NAME", "firstDB");
?>
db-connect.php
(script kết nối cơ sở dữ liệu)
<?php
include_once 'config.php';
class DbConnect{
private $connect;
public function __construct(){
$this->connect = mysqli_connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if (mysqli_connect_errno($this->connect)){
echo "Unable to connect to MySQL Database: " . mysqli_connect_error();
}
}
public function getDb(){
return $this->connect;
}
}
?>
user.php
(script chứa tất cả các hàm chính của ứng dụng)
<?php
include_once 'db-connect.php';
class User{
private $db;
private $db_table = "users";
public function __construct(){
$this->db = new DbConnect();
}
public function isLoginExist($username, $password){
$query = "select * from ".$this->db_table." where username = '$username' AND password = '$password' Limit 1";
$result = mysqli_query($this->db->getDb(), $query);
if(mysqli_num_rows($result) > 0){
mysqli_close($this->db->getDb());
return true;
}
mysqli_close($this->db->getDb());
return false;
}
public function isEmailUsernameExist($username, $email){
$query = "select * from ".$this->db_table." where username = '$username' AND email = '$email'";
$result = mysqli_query($this->db->getDb(), $query);
if(mysqli_num_rows($result) > 0){
mysqli_close($this->db->getDb());
return true;
}
return false;
}
public function isValidEmail($email){
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
public function createNewRegisterUser($username, $password, $email){
$isExisting = $this->isEmailUsernameExist($username, $email);
if($isExisting){
$json['success'] = 0;
$json['message'] = "Error in registering. Probably the username/email already exists";
}
else{
$isValid = $this->isValidEmail($email);
if($isValid)
{
$query = "insert into ".$this->db_table." (username, password, email, created_at, updated_at) values ('$username', '$password', '$email', NOW(), NOW())";
$inserted = mysqli_query($this->db->getDb(), $query);
if($inserted == 1){
$json['success'] = 1;
$json['message'] = "Successfully registered the user";
}else{
$json['success'] = 0;
$json['message'] = "Error in registering. Probably the username/email already exists";
}
mysqli_close($this->db->getDb());
}
else{
$json['success'] = 0;
$json['message'] = "Error in registering. Email Address is not valid";
}
}
return $json;
}
public function loginUsers($username, $password){
$json = array();
$canUserLogin = $this->isLoginExist($username, $password);
if($canUserLogin){
$json['success'] = 1;
$json['message'] = "Successfully logged in";
}else{
$json['success'] = 0;
$json['message'] = "Incorrect details";
}
return $json;
}
}
?>
Trong đoạn code trên, $json
chứa các JSONObject được trả về. Script PHP sau đây là script được gọi đầu tiên từ ứng dụng.
index.php
<?php
require_once 'user.php';
$username = "";
$password = "";
$email = "";
if(isset($_POST['username'])){
$username = $_POST['username'];
}
if(isset($_POST['password'])){
$password = $_POST['password'];
}
if(isset($_POST['email'])){
$email = $_POST['email'];
}
$userObject = new User();
// Registration
if(!empty($username) && !empty($password) && !empty($email)){
$hashed_password = md5($password);
$json_registration = $userObject->createNewRegisterUser($username, $hashed_password, $email);
echo json_encode($json_registration);
}
// Login
if(!empty($username) && !empty($password) && empty($email)){
$hashed_password = md5($password);
$json_array = $userObject->loginUsers($username, $hashed_password);
echo json_encode($json_array);
}
?>
Trong đoạn code trên, chúng ta kiểm tra xem trường email có trống hay không. Nếu có, chúng ta sẽ gọi hàm đăng nhập trong script PHP. Nếu không, chúng ta sẽ chuyển đến hàm đăng ký.
Phản hồi JSON trả về hai tham số: success (0 hoặc 1) và message.
- Hàm
md5()
sử dụng thuật toán băm MD5 để tạo một chuỗi hash của mật khẩu. - Để kiểm tra xem địa chỉ email có hợp lệ không, chúng ta đã dùng hàm
isValidEmail()
. FILTER_VALIDATE_EMAIL hoạt động trên các phiên bản PHP 5.2.0 trở lên.
Cấu trúc project đăng nhập và đăng ký trên Android
Trong ứng dụng này, chúng ta đã sử dụng ba thư viện để thực hiện các lệnh gọi HTTP trong ứng dụng của mình. Lớp JSONParser được sử dụng để thực hiện các lệnh gọi HTTP POST và GET đến localhost và trả về phản hồi dưới dạng một JSONObject.
Code đăng nhập và đăng ký trên Android
Layout activity_main.xml
được định nghĩa như bên dưới.
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="<https://schemas.android.com/apk/res/android>"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:fillViewport="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:id="@+id/linearLayout">
<EditText android:id="@+id/editName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"
android:textColor="#FF192133"
android:textColorHint="#A0192133"
android:fontFamily="sans-serif-light"
android:focusable="true"
android:focusableInTouchMode="true" />
<EditText android:id="@+id/editPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:textColor="#FF192133"
android:textColorHint="#A0192133"
android:fontFamily="sans-serif-light"
android:hint="Password"
android:focusable="true"
android:focusableInTouchMode="true" />
<EditText android:id="@+id/editEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:textColor="#FF192133"
android:visibility="gone"
android:textColorHint="#A0192133"
android:fontFamily="sans-serif-light"
android:hint="Email"
android:focusable="true"
android:focusableInTouchMode="true" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btnSignIn"
android:text="SIGN IN"
android:textStyle="bold"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/btnRegister"
android:text="REGISTER"
android:textStyle="bold"
/>
</LinearLayout>
</RelativeLayout>
</ScrollView>
File MainActivity.java
có nội dung như sau.
package com.journaldev.loginphpmysql;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
EditText editEmail, editPassword, editName;
Button btnSignIn, btnRegister;
String URL= "<https://10.0.3.2/test_android/index.php>";
JSONParser jsonParser=new JSONParser();
int i=0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editEmail=(EditText)findViewById(R.id.editEmail);
editName=(EditText)findViewById(R.id.editName);
editPassword=(EditText)findViewById(R.id.editPassword);
btnSignIn=(Button)findViewById(R.id.btnSignIn);
btnRegister=(Button)findViewById(R.id.btnRegister);
btnSignIn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AttemptLogin attemptLogin= new AttemptLogin();
attemptLogin.execute(editName.getText().toString(),editPassword.getText().toString(),"");
}
});
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(i==0)
{
i=1;
editEmail.setVisibility(View.VISIBLE);
btnSignIn.setVisibility(View.GONE);
btnRegister.setText("CREATE ACCOUNT");
}
else{
btnRegister.setText("REGISTER");
editEmail.setVisibility(View.GONE);
btnSignIn.setVisibility(View.VISIBLE);
i=0;
AttemptLogin attemptLogin= new AttemptLogin();
attemptLogin.execute(editName.getText().toString(),editPassword.getText().toString(),editEmail.getText().toString());
}
}
});
}
private class AttemptLogin extends AsyncTask<String, String, JSONObject> {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected JSONObject doInBackground(String... args) {
String email = args[2];
String password = args[1];
String name= args[0];
ArrayList params = new ArrayList();
params.add(new BasicNameValuePair("username", name));
params.add(new BasicNameValuePair("password", password));
if(email.length()>0)
params.add(new BasicNameValuePair("email",email));
JSONObject json = jsonParser.makeHttpRequest(URL, "POST", params);
return json;
}
protected void onPostExecute(JSONObject result) {
// dismiss the dialog once product deleted
//Toast.makeText(getApplicationContext(),result,Toast.LENGTH_LONG).show();
try {
if (result != null) {
Toast.makeText(getApplicationContext(),result.getString("message"),Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getApplicationContext(), "Unable to retrieve any data from server", Toast.LENGTH_LONG).show();
}
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
Đoạn code này khá dài, nhưng ta có thể rút ra những điểm quan trọng như sau:
https://10.0.3.2
là địa chỉ chuyển hướng localhost. Địa chỉ này hoạt động riêng biệt nếu bạn đang sử dụng trình giả lập Genymotion. Sử dụnghttps://10.0.2.2
cho trình giả lập AVD (đã có nhiều cải tiến gần đây). Nếu bạn đang chạy ứng dụng trên thiết bị của riêng mình, hãy sử dụng địa chỉ Wi-Fi của máy tính của bạn. Ví dụ:https://192.168.0.143
.- Khi nút REGISTER được ấn, chương trình ẩn nút SIGN IN và hiển thị trường nhập văn bản Email Address thay thế.
- Lớp
AttemptLogin
chạy nền các request HTTP đến localhost của chúng ta. Các tham số username, password và email được thêm vào một ArrayList, sau đó được truyền vào phương thứcmakeHttpRequest(URL, “POST”, params);
của lớpJSONParser
. - Trong phương thức
onPostExecute
, chúng ta hiển thị chuỗi message được trả về từ server trong một thông báo Toast.
Lớp JSONParser.java
có nội dung dưới đây:
package com.journaldev.loginphpmysql;
import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
/**
* Created by anupamchugh on 29/08/16.
*/
public class JSONParser {
static InputStream is = null;
static JSONObject jObj = null;
static JSONArray jArr = null;
static String json = "";
static String error = "";
// constructor
public JSONParser() {
}
// function get json from url
// by making HTTP POST or GET mehtod
public JSONObject makeHttpRequest(String url, String method,
ArrayList params) {
// Making HTTP request
try {
// check for request method
if(method.equals("POST")){
// request method is POST
// defaultHttpClient
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
httpPost.setEntity(new UrlEncodedFormEntity(params));
try {
Log.e("API123", " " +convertStreamToString(httpPost.getEntity().getContent()));
Log.e("API123",httpPost.getURI().toString());
} catch (Exception e) {
e.printStackTrace();
}
HttpResponse httpResponse = httpClient.execute(httpPost);
Log.e("API123",""+httpResponse.getStatusLine().getStatusCode());
error= String.valueOf(httpResponse.getStatusLine().getStatusCode());
HttpEntity httpEntity = httpResponse.getEntity();
is = httpEntity.getContent();
}else if(method.equals("GET")){
// request method is GET
DefaultHttpClient httpClient = new DefaultHttpClient();
String paramString = URLEncodedUtils.format(params, "utf-8");
url += "?" + paramString;
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(httpGet);
HttpEntity httpEntity = httpResponse.getEntity();
is = httpEntity.getContent();
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(
is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\\n");
}
is.close();
json = sb.toString();
Log.d("API123",json);
} catch (Exception e) {
Log.e("Buffer Error", "Error converting result " + e.toString());
}
// try to parse the string to a JSON object
try {
jObj = new JSONObject(json);
jObj.put("error_code",error);
} catch (JSONException e) {
Log.e("JSON Parser", "Error parsing data " + e.toString());
}
// return JSON String
return jObj;
}
private String convertStreamToString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
is.close();
return sb.toString();
}
}
Trong đoạn code trên, chúng ta đang gọi các lớp tương ứng là HTTPPost hoặc HTTPGet tùy thuộc vào tham số thứ hai được truyền vào hàm makeHttpRequest
.
jObj.put("error_code",error);
Ở trên, chúng ta đang nối thêm mã trạng thái phản hồi được trả về từ server vào JSONObject cuối cùng được trả về cho lớp MainActivity. Lưu ý: Đừng quên thêm quyền sau vào file AndroidManifest.xml
của bạn.
<uses-permission android:name="android.permission.INTERNET"/>
Nhiều bạn đọc đã phản hồi là họ gặp phải thông báo Toast “Unable to retrieve data”. Lưu ý rằng kể từ Android 6.0 trở lên, bạn cần thêm thuộc tính sau vào thẻ application trong file Manifest.xml
của mình: android:usesCleartextTraffic="true"
. Lý do là để cho phép bảo mật mạng của trình giả lập/thiết bị thực hiện các lệnh gọi http.
Hãy kiểm tra kết quả với các ảnh chụp màn hình mới nhất từ trình giả lập Android Q bên dưới. Mã nguồn mới nhất với các thay đổi trong file AndroidManifest.xml đã được cập nhật trong liên kết và repo Github ở cuối bài.
Ở dưới là kết quả thực tế ứng dụng khi hoạt động.
Đăng ký người dùng trên Android
Trong clip bên dưới, chúng ta đăng ký một người dùng mới và người dùng đó được thêm vào cơ sở dữ liệu. Sau đó, chúng ta đăng nhập bằng thông tin đăng nhập đã nhập trong quá trình đăng ký.
Tổng kết
Bạn có thể tải xuống toàn bộ project trên từ đường link này. Nó có chứa thư mục test_android
chứa các file PHP. Hãy sao chép nó vào thư mục xampp->htdocs
của bạn. Ngoài ra, bạn cũng có thể tải code đầy đủ từ repo Github này.