Giới thiệu
Để truyền object giữa các Activity, object phải implement một trong 2 interface:
- Serializable (Java standard) - Dễ dùng, chậm hơn, dùng reflection.
- Parcelable (Android optimized) - Nhanh hơn, cần viết boilerplate code (hoặc dùng
@ParcelizeKotlin).
Bài viết hướng dẫn cả 2 cách, ưu tiên Parcelable với @Parcelize (Kotlin).
Các bước thực hiện
Bước 1: Tạo dự án mới
Android Studio → File → New Project → Empty View Activity.
Bước 2: Tạo Model Class
Cách 1: Serializable (Java/Kotlin) - Đơn giản
// Java
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
private String content;
private int priority;
private boolean isRead;
public Message(String content, int priority, boolean isRead) {
this.content = content;
this.priority = priority;
this.isRead = isRead;
}
// Getters, setters, toString()...
}
// Kotlin
data class Message(
val content: String,
val priority: Int,
val isRead: Boolean
) : Serializable
Cách 2: Parcelable với @Parcelize (Kotlin - Khuyến nghị)
// build.gradle.kts (plugins)
id("org.jetbrains.kotlin.plugin.parcelize") version "1.9.0" apply true
// Model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class Message(
val content: String,
val priority: Int,
val isRead: Boolean,
val timestamp: Long = System.currentTimeMillis()
) : Parcelable
Cách 3: Parcelable thủ công (Java - không khuyến nghị)
public class Message implements Parcelable {
private String content;
private int priority;
private boolean isRead;
// Constructor, getters, setters...
protected Message(Parcel in) {
content = in.readString();
priority = in.readInt();
isRead = in.readByte() != 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(content);
dest.writeInt(priority);
dest.writeByte((byte) (isRead ? 1 : 0));
}
@Override
public int describeContents() { return 0; }
public static final Creator CREATOR = new Creator() {
@Override
public Message createFromParcel(Parcel in) { return new Message(in); }
@Override
public Message[] newArray(int size) { return new Message[size]; }
};
}
Bước 3: Layout Activity 1 (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".MainActivity">
<EditText
android:id="@+id/etMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nội dung tin nhắn"
android:inputType="textMultiLine"
android:minHeight="100dp"
android:padding="12dp"
android:background="@drawable/edittext_rounded" />
<Button
android:id="@+id/btnSendObject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Gửi Object sang Activity 2" />
</LinearLayout>
Bước 4: Layout Activity 2 (res/layout/activity_second.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center"
tools:context=".SecondActivity">
<TextView
android:id="@+id/tvReceived"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Received: Message[content=Hello, priority=1, isRead=false]" />
</LinearLayout>
Bước 5: MainActivity.kt (Gửi object)
package com.example.passobject
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.passobject.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnSendObject.setOnClickListener {
val content = binding.etMessage.text.toString().trim()
if (content.isEmpty()) {
Toast.makeText(this, "Vui lòng nhập nội dung", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
val message = Message(
content = content,
priority = 1,
isRead = false
)
val intent = Intent(this, SecondActivity::class.java)
// Cách 1: Parcelable (khuyến nghị)
intent.putExtra("message", message)
// Cách 2: Serializable (nếu dùng Serializable)
// intent.putExtra("message", message) // Cùng API, tự detect
startActivity(intent)
}
}
}
Bước 6: SecondActivity.kt (Nhận object)
package com.example.passobject
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.passobject.databinding.ActivitySecondBinding
class SecondActivity : AppCompatActivity() {
private lateinit var binding: ActivitySecondBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySecondBinding.inflate(layoutInflater)
setContentView(binding.root)
// Nhận Parcelable
val message = intent.getParcelableExtra("message")
// Nhận Serializable (cách cũ)
// val message = intent.getSerializableExtra("message") as? Message
if (message != null) {
binding.tvReceived.text = "Received: $message"
} else {
binding.tvReceived.text = "Không nhận được object"
}
}
}
Bước 7: AndroidManifest.xml
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SecondActivity" android:exported="false" />
Truyền Object qua Fragment (Arguments Bundle)
// Gửi từ Activity/Fragment
val fragment = MyFragment()
val args = Bundle().apply {
putParcelable("message", message) // Parcelable
// putSerializable("message", message) // Serializable
}
fragment.setArguments(args)
// Nhận trong Fragment
val message = arguments?.getParcelable("message")
Truyền Object lớn / Complex (Không khuyến nghị qua Intent)
Intent có giới hạn kích thước (~1MB, tùy thiết bị). Đối với object lớn (bitmap, list lớn, JSON to):
- ViewModel chia sẻ (Activity + Fragment, hoặc Fragment + Fragment): `activityViewModels()` hoặc `sharedViewModel`.
- Repository/Singleton lưu trữ tạm.
- File/Cache ghi tạm, đọc ở destination.
- WorkManager/DataStore cho persistent data.
So sánh Serializable vs Parcelable
| Tiêu chí | Serializable | Parcelable (@Parcelize) |
|---|---|---|
| Performance | Chậm (reflection, I/O) | Nhanh (tối ưu Android) |
| Code boilerplate | Ít (implements Serializable) | Rất ít (@Parcelize data class) |
| Kích thước object | Lớn hơn | Nhỏ hơn |
| Versioning | serialVersionUID | Tự động (thêm field mới OK) |
| Khuyến nghị | Chỉ cho object đơn giản, ít dùng | Mặc định cho mọi trường hợp |
Lưu ý quan trọng
- Luôn dùng
@Parcelizecho Kotlin data class - tự generate code Parcelable. - Object Parcelable/Serializable phải có **constructor không tham số** hoặc tất cả field có default value (data class tự có).
- Nested object: Tất cả class con cũng phải implement Parcelable/Serializable.
- Bitmap: Implement Parcelable (có sẵn) nhưng KHÔNG nên truyền Bitmap qua Intent (quá lớn) - truyền URI/path/file.
intent.getParcelableExtrayêu cầu API 33+ (Kotlin extension). API cũ:("key") intent.getParcelableExtra("key") as Type?.