Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách truyền dữ liệu từ Activity sang Fragment Android (Bundle/Arguments)

Giới thiệu

Truyền data từ Activity → Fragment dùng Bundle + setArguments() (trước khi add fragment) hoặc Fragment Result API (hiện đại, type-safe). Không dùng static field hoặc singleton - gây leak và lỗi khi recreate.

Các cách truyền dữ liệu

Cách 1: Bundle + setArguments() (Cổ điển, vẫn dùng được)

Phù hợp: Fragment được add lần đầu từ Activity.

Fragment (MyFragment.kt)

package com.example.activitytofragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.activitytofragment.databinding.FragmentMyBinding

class MyFragment : Fragment() {

    private lateinit var binding: FragmentMyBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View {
        binding = FragmentMyBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // Lấy arguments
        val message = arguments?.getString("message") ?: "Không có dữ liệu"
        binding.tvMessage.text = message
    }
}

Layout Fragment (res/layout/fragment_my.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center">

    <TextView
        android:id="@+id/tvMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textStyle="bold"
        android:gravity="center" />
</LinearLayout>

MainActivity.kt (Gửi data)

package com.example.activitytofragment

import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentTransaction
import com.example.activitytofragment.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.btnSendData.setOnClickListener { sendDataToFragment() }
    }

    private fun sendDataToFragment() {
        val message = binding.etMessage.text.toString().trim()
        if (message.isEmpty()) return

        // Tạo fragment instance
        val fragment = MyFragment()
        
        // Đóng gói data vào Bundle
        val args = Bundle().apply {
            putString("message", message)
            // Có thể put nhiều loại: putInt, putParcelable, putSerializable...
        }
        fragment.arguments = args // hoặc fragment.setArguments(args)

        // Add fragment vào container
        supportFragmentManager.beginTransaction()
            .replace(R.id.fragmentContainer, fragment)
            .commit()
    }
}

Layout Activity (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"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Nhập tin nhắn gửi Fragment"
        android:padding="12dp"
        android:background="@drawable/edittext_rounded" />

    <Button
        android:id="@+id/btnSendData"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Gửi dữ liệu sang Fragment" />

    <FrameLayout
        android:id="@+id/fragmentContainer"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginTop="16dp" />
</LinearLayout>

Cách 2: Fragment Result API (Hiện đại, Type-safe, Khuyến nghị)

Dùng setFragmentResultListener / setFragmentResult - hoạt động qua FragmentManager, survive configuration change.

Fragment nhận kết quả (MyFragment.kt)

class MyFragment : Fragment() {

    private lateinit var binding: FragmentMyBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // Đăng ký listener CHO TRƯỚC khi Activity gửi
        setFragmentResultListener("message_key") { requestKey, bundle ->
            val message = bundle.getString("message") ?: ""
            // Cập nhật UI (cần view đã tạo)
            binding?.tvMessage?.text = message
        }
    }

    override fun onCreateView(...) { ... }
}

Activity gửi kết quả (MainActivity.kt)


binding.btnSendData.setOnClickListener {
    val message = binding.etMessage.text.toString()
    
    // Gửi kết quả đến Fragment đang hiển thị trong container
    supportFragmentManager.setFragmentResult("message_key", 
        Bundle().apply { putString("message", message) }
    )
}

Cách 3: Shared ViewModel (Best cho komunikasi 2 chiều, lifecycle-aware)

Activity và Fragment share cùng một ViewModel instance.

SharedViewModel.kt


import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class SharedViewModel : ViewModel() {
    val message = MutableLiveData()
}

Activity


private val viewModel: SharedViewModel by activityViewModels()

binding.btnSendData.setOnClickListener {
    viewModel.message.value = binding.etMessage.text.toString()
}

Fragment


private val viewModel: SharedViewModel by activityViewModels()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    viewModel.message.observe(viewLifecycleOwner) { message ->
        binding.tvMessage.text = message
    }
}

So sánh 3 cách

Tiêu chíBundle + setArgumentsFragment Result APIShared ViewModel
Độ phức tạpThấpTrung bìnhCao hơn
Survive config changeCó (Bundle tự restore)CóCó (ViewModel survive)
Type-safeKhông (string key)Không (string key)Có (LiveData)
2 chiều (Fragment → Activity)Khó (interface/callback)Có (setFragmentResult)Dễ dàng (LiveData)
Phù hợpData đơn giản, one-time initRequest/Response, callbackShared state, complex UI

Truyền Object (Parcelable/Serializable)


// Bundle
val args = Bundle().apply {
    putParcelable("user", user) // Parcelable
    // putSerializable("user", user) // Serializable
}
fragment.arguments = args

// Fragment nhận
val user = arguments?.getParcelable("user")

Lưu ý quan trọng

  • Luôn set arguments trước khi add fragment vào FragmentManager.
  • Fragment recreate (rotate screen): arguments tự động restore.
  • Không dùng fragment.message = "..." (property trực tiếp) - mất data khi recreate.
  • Fragment Result API: Key phải unique (dùng constant string).
  • Shared ViewModel: Dùng activityViewModels() (Activity scope) hoặc viewModels() (Fragment scope).
  • Navigation Component: Dùng Safe Args (type-safe navigation arguments) - khuyến nghị nhất cho project mới.

Navigation Component + Safe Args (Best Practice hiện tại)

// nav_graph.xml

    


// Activity - Navigate with args
val action = MainFragmentDirections.actionMainToMyFragment(message = "Hello")
findNavController().navigate(action)

// Fragment - Receive args (type-safe)
val message = MyFragmentArgs.fromBundle(arguments).message