Chủ đề này tập trung vào một số khía cạnh hữu ích nhất của ngôn ngữ Kotlin khi phát triển Android.
Làm việc với fragment (phân đoạn)
Các phần sau đây sử dụng ví dụ về Fragment
để làm nổi bật một số tính năng tốt nhất của Kotlin.
Tính kế thừa
Bạn có thể khai báo một lớp trong Kotlin bằng từ khoá class
. Trong ví dụ
sau, LoginFragment
là lớp con của Fragment
. Bạn có thể chỉ ra
tính kế thừa bằng cách sử dụng toán tử :
giữa lớp con và lớp gốc:
class LoginFragment : Fragment()
Trong khai báo về lớp này, LoginFragment
chịu trách nhiệm gọi
hàm dựng của lớp cấp cao hơn, Fragment
.
Trong LoginFragment
, bạn có thể ghi đè một số phương thức gọi lại trong vòng đời để
phản hồi các thay đổi về trạng thái trong Fragment
. Để ghi đè một hàm, hãy sử dụng từ khoá
override
, như trong ví dụ sau:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.login_fragment, container, false)
}
Để tham chiếu đến một hàm trong lớp gốc, hãy sử dụng từ khoá super
, như
trong ví dụ sau:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
Tính chất rỗng và thao tác khởi tạo
Trong những ví dụ trước, một số thông số trong các phương thức bị ghi đè có
các loại mang hậu tố là dấu chấm hỏi ?
. Đây là dấu hiệu cho biết rằng các đối số
đã truyền cho các thông số này có thể nhận giá trị rỗng. Hãy đảm bảo bạn
xử lý tính chất rỗng một cách an toàn.
Trong Kotlin, bạn phải khởi tạo các thuộc tính của đối tượng khi khai báo đối tượng.
Điều này có nghĩa là khi có được thực thể của một lớp, bạn có thể
tham chiếu ngay mọi thuộc tính truy cập được của lớp đó. Tuy nhiên, các đối tượng View
trong Fragment
, chưa sẵn sàng để tăng cường cho đến khi gọi Fragment#onCreateView
, vì vậy, bạn cần có một cách để trì hoãn việc khởi tạo thuộc tính cho View
.
lateinit
cho phép bạn trì hoãn việc khởi tạo thuộc tính. Khi sử dụng lateinit
, bạn nên khởi tạo thuộc tính của mình càng sớm càng tốt.
Ví dụ sau minh hoạ việc sử dụng lateinit
để gán các đối tượng View
trong
onViewCreated
:
class LoginFragment : Fragment() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
usernameEditText = view.findViewById(R.id.username_edit_text)
passwordEditText = view.findViewById(R.id.password_edit_text)
loginButton = view.findViewById(R.id.login_button)
statusTextView = view.findViewById(R.id.status_text_view)
}
...
}
Chuyển đổi SAM
Bạn có thể theo dõi các sự kiện nhấp chuột trong Android bằng cách triển khai
giao diện OnClickListener
. Đối tượng Button
chứa một hàm setOnClickListener()
có mã triển khai của OnClickListener
.
OnClickListener
có một phương thức đơn trừu tượng là onClick()
mà bạn phải
triển khai. Vì setOnClickListener()
luôn lấy OnClickListener
làm
đối số và vì OnClickListener
luôn có cùng một phương thức đơn trừu tượng, nên cách triển khai này có thể được biểu thị bằng cách sử dụng hàm ẩn danh trong Kotlin. Quá trình này gọi là
chuyển đổi phương pháp trừu tượng duy nhất
hay còn gọi là chuyển đổi SAM.
Việc chuyển đổi SAM có thể giúp mã của bạn gọn gàng hơn. Ví dụ sau
cho biết cách chuyển đổi SAM để triển khai OnClickListener
cho
Button
:
loginButton.setOnClickListener {
val authSuccessful: Boolean = viewModel.authenticate(
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
if (authSuccessful) {
// Navigate to next screen
} else {
statusTextView.text = requireContext().getString(R.string.auth_failed)
}
}
Mã trong hàm ẩn danh được truyền đến setOnClickListener()
sẽ thực thi khi người dùng nhấp vào loginButton
.
Đối tượng companion (đồng hành)
Đối tượng companion cung cấp cơ chế xác định các biến hoặc hàm được liên kết về mặt khái niệm với một loại nhưng không gắn với một đối tượng cụ thể nào. Đối tượng companion hoạt động tương tự như việc sử dụng từ khoá static
của Java cho biến và phương thức.
Trong ví dụ sau, TAG
là hằng String
. Bạn không cần có
một thực thể duy nhất của String
cho mỗi thực thể của LoginFragment
. Vì vậy, bạn nên xác định nó trong một đối tượng companion:
class LoginFragment : Fragment() {
...
companion object {
private const val TAG = "LoginFragment"
}
}
Bạn có thể xác định TAG
ở cấp cao nhất của tệp, nhưng tệp đó cũng có thể có nhiều biến, hàm và lớp cũng được xác định ở cấp cao nhất. Các đối tượng companion giúp kết nối
các biến, hàm và định nghĩa lớp mà không cần tham chiếu đến
bất kỳ thực thể cụ thể nào của lớp đó.
Uỷ quyền thuộc tính
Khi khởi tạo các thuộc tính, bạn có thể lặp lại một số mẫu phổ biến của Android, chẳng hạn như truy cập vào một ViewModel
trong Fragment
. Để tránh việc có quá nhiều mã trùng lặp, bạn có thể sử dụng cú pháp uỷ quyền thuộc tính của Kotlin.
private val viewModel: LoginViewModel by viewModels()
Cú pháp uỷ quyền thuộc tính cung cấp một mã triển khai phổ biến mà bạn có thể sử dụng lại trong toàn bộ ứng dụng của mình. KTX Android cung cấp cho bạn một số đại biểu thuộc tính cho bạn.
Ví dụ: viewModels
sẽ truy xuất ViewModel
trong phạm vi Fragment
hiện tại.
Cú pháp uỷ quyền sử dụng quy trình phản chiếu, làm tiêu tốn thêm tài nguyên. Đổi lại, bạn sẽ có cú pháp ngắn gọn giúp tiết kiệm thời gian phát triển.
Tính chất rỗng
Kotlin cung cấp các quy tắc nghiêm ngặt về tính chất rỗng để duy trì độ an toàn của loại trong toàn bộ
ứng dụng của bạn. Theo mặc định, trong Kotlin, các tham chiếu đến đối tượng không thể chứa giá trị rỗng. Để chỉ định giá trị rỗng cho một biến, bạn phải khai báo loại biến có thể nhận giá trị rỗng bằng cách thêm ?
vào cuối loại cơ sở.
Ví dụ: biểu thức sau đây là không hợp lệ trong Kotlin. name
thuộc loại
String
và không thể nhận giá trị rỗng:
val name: String = null
Để cho phép giá trị rỗng, bạn phải sử dụng loại String
có thể nhận giá trị rỗng, String?
, như trong ví dụ sau:
val name: String? = null
Khả năng tương thích
Các quy tắc nghiêm ngặt của Kotlin giúp mã của bạn an toàn và súc tích hơn. Các quy tắc này làm giảm nguy cơ có NullPointerException
sẽ khiến ứng dụng của bạn
bị lỗi. Ngoài ra, các thuộc tính này giúp giảm số lần kiểm tra giá trị rỗng mà bạn cần thực hiện trong mã.
Thông thường, bạn còn phải gọi mã không phải Kotlin khi viết một ứng dụng Android, vì hầu hết các API của Android đều được viết bằng ngôn ngữ lập trình Java.
Tính chất rỗng là một vấn đề chính mà trong đó, Java và Kotlin có hành vi khác nhau. Java có quy tắc ít nghiêm ngặt hơn về cú pháp tính chất rỗng.
Ví dụ: lớp Account
có một vài thuộc tính, trong đó có một thuộc tính String
được gọi là name
. Java không có quy tắc giống Kotlin về tính chất rỗng, thay vào đó, hãy dựa vào các chú giải tính chất rỗng (không bắt buộc) để khai báo rõ ràng liệu bạn có thể chỉ định giá trị rỗng hay không.
Vì khung Android chủ yếu được viết bằng Java, nên bạn có thể gặp phải trường hợp này khi gọi API mà không có chú giải tính chất rỗng.
Loại nền tảng
Nếu bạn sử dụng Kotlin để tham chiếu một thành phần name
chưa chú giải đã được xác định trong một
lớp Account
trong Java, thì trình biên dịch sẽ không biết liệu String
liên kết tới
String
hay String?
trong Kotlin. Tình huống không rõ ràng này được biểu thị qua một
loại nền tảng là String!
.
String!
không có ý nghĩa đặc biệt đối với trình biên dịch Kotlin. String!
có thể biểu thị
String
hoặc String?
, và trình biên dịch cho phép bạn gán giá trị
của một trong hai loại này. Lưu ý rằng có nguy cơ bạn sẽ gửi NullPointerException
nếu bạn biểu thị loại này dưới dạng String
và chỉ định giá trị rỗng.
Để giải quyết vấn đề này, bạn nên sử dụng chú giải tính chất rỗng bất cứ khi nào bạn viết mã trong Java. Các chú giải này giúp ích cho nhà phát triển của cả Java và Kotlin.
Ví dụ: sau đây là lớp Account
như được xác định trong Java:
public class Account implements Parcelable {
public final String name;
public final String type;
private final @Nullable String accessId;
...
}
Một trong các biến thành phần (accessId
) được chú giải bằng @Nullable
để cho biết biến này có thể chứa giá trị rỗng. Sau đó, Kotlin sẽ coi accessId
là String?
.
Để cho biết rằng một biến không thể có giá trị rỗng, hãy sử dụng chú giải @NonNull
:
public class Account implements Parcelable {
public final @NonNull String name;
...
}
Trong trường hợp này, name
được coi là String
không thể nhận giá trị rỗng trong Kotlin.
Chú giải tính chất rỗng có trong tất cả API mới và nhiều API hiện có của Android. Nhiều thư viện Java đã thêm chú giải tính chất rỗng vào để hỗ trợ tốt hơn cho nhà phát triển trên cả Kotlin và Java.
Xử lý tính chất rỗng
Nếu không chắc chắn về một loại của Java, bạn nên xem loại đó là có thể nhận giá trị rỗng.
Ví dụ: thành phần name
của lớp Account
không được chú thích, do đó, bạn nên mặc định cho rằng đó là một String
có thể nhận giá trị rỗng.
Nếu muốn cắt ngắn name
để giá trị của nó không bao gồm dấu cách ở đầu hoặc ở cuối, thì bạn có thể sử dụng hàm trim
của Kotlin. Bạn có thể cắt ngắn
String?
theo một số cách an toàn. Một trong những cách này là sử dụng toán tử xác nhận
giá trị khác rỗng, !!
, như trong ví dụ sau:
val account = Account("name", "type")
val accountName = account.name!!.trim()
Toán tử !!
coi tất cả mọi thứ ở bên trái là giá trị khác rỗng, do đó, trong trường hợp này, bạn sẽ coi name
là String
khác rỗng. Nếu kết quả của biểu thức
ở bên trái là giá trị rỗng, thì ứng dụng sẽ gửi một NullPointerException
.
Toán tử này hoạt động nhanh chóng và dễ dàng, nhưng bạn nên sử dụng một cách thận trọng, vì toán tử này có thể
đưa lại các thực thể của NullPointerException
vào mã của bạn.
Lựa chọn an toàn hơn là sử dụng toán tử an toàn cho lệnh gọi, ?.
, như trong
ví dụ sau:
val account = Account("name", "type")
val accountName = account.name?.trim()
Sử dụng toán tử an toàn cho lệnh gọi, nếu name
khác rỗng, thì kết quả của
name?.trim()
sẽ là một giá trị tên không có khoảng trắng ở đầu hoặc cuối. Nếu
name
là giá trị rỗng, thì kết quả của name?.trim()
sẽ là null
. Điều này có nghĩa là ứng dụng của bạn sẽ không bao giờ gửi NullPointerException
khi thực thi câu lệnh này.
Mặc dù toán tử an toàn cho lệnh gọi giúp bạn tránh nguy cơ có NullPointerException
, nhưng
toán tử này sẽ truyền giá trị rỗng cho câu lệnh tiếp theo. Thay vào đó, bạn có thể xử lý
các trường hợp giá trị rỗng ngay lập tức bằng cách sử dụng toán tử Elvis (?:
), như trong ví dụ
sau:
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
Nếu kết quả của biểu thức ở phía bên trái của toán tử Elvis là giá trị rỗng, thì giá trị ở bên phải sẽ được gán cho accountName
. Kỹ thuật này rất hữu ích khi cung cấp một giá trị mặc định có thể rỗng.
Bạn cũng có thể sử dụng toán tử Elvis để sớm trả về từ một hàm, như trong ví dụ sau:
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
Thay đổi về API của Android
Các API của Android đang ngày càng trở nên tương thích với Kotlin. Nhiều API phổ biến nhất của Android, bao gồm AppCompatActivity
và Fragment
, chứa chú giải tính chất rỗng, một số lệnh gọi như Fragment#getContext
có lựa chọn thay thế phù hợp cho Kotlin.
Ví dụ: việc truy cập Context
của Fragment
hầu như luôn có giá trị khác rỗng, vì hầu hết các lệnh gọi bạn thực hiện trong Fragment
xảy ra trong khi Fragment
được đính kèm với Activity
(một lớp con của Context
). Tuy nhiên,
Fragment#getContext
không phải lúc nào cũng trả về giá trị khác rỗng, vì
có những trường hợp Fragment
không được đính kèm với Activity
. Do đó, loại dữ liệu trả về của Fragment#getContext
có thể nhận giá trị rỗng.
Vì Context
được trả về từ Fragment#getContext
không thể nhận giá trị rỗng (và được chú giải là @Nullable), nên bạn phải coi đây là Context?
trong mã Kotlin.
Như vậy có nghĩa là bạn sẽ áp dụng một trong các toán tử đã đề cập trước đó để xử lý
tính chất rỗng trước khi truy cập vào các thuộc tính và hàm của toán tử đó. Đối với một số trường hợp như vậy, Android chứa các API thay thế để mang lại sự tiện lợi này.
Ví dụ: Fragment#requireContext
trả về một Context
khác rỗng và gửi một IllegalStateException
nếu được gọi khi Context
mang giá trị rỗng. Bằng cách này,
bạn có thể coi Context
kết quả là giá trị khác rỗng mà không cần
sử dụng các toán tử an toàn cho lệnh gọi hoặc giải pháp thay thế.
Khởi tạo thuộc tính
Theo mặc định, các thuộc tính trong Kotlin không được khởi tạo. Bạn phải khởi tạo các thuộc tính này khi khởi tạo lớp bao hàm.
Bạn có thể khởi tạo các thuộc tính theo một vài cách. Ví dụ sau
cho thấy cách khởi tạo biến index
bằng cách gán giá trị cho biến đó trong
phần khai báo của lớp:
class LoginFragment : Fragment() {
val index: Int = 12
}
Việc khởi tạo này cũng có thể được xác định trong một khối khởi tạo:
class LoginFragment : Fragment() {
val index: Int
init {
index = 12
}
}
Trong các ví dụ ở trên, index
được khởi tạo khi tạo LoginFragment
.
Tuy nhiên, có thể bạn sẽ không khởi tạo được một số thuộc tính trong quá trình tạo
đối tượng. Ví dụ: Bạn nên tham chiếu đến một View
từ bên trong Fragment
, nghĩa là trước tiên, bố cục đó phải được tăng cường. Việc tăng cường không xảy ra khi tạo Fragment
. Thay vào đó, bố cục sẽ được tăng cường khi gọi Fragment#onCreateView
.
Có một cách để giải quyết trường hợp này là khai báo chế độ xem là không thể nhận giá trị rỗng và khởi tạo chế độ xem đó càng sớm càng tốt, như trong ví dụ sau:
class LoginFragment : Fragment() {
private var statusTextView: TextView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView?.setText(R.string.auth_failed)
}
}
Mặc dù cách này có hiệu quả như mong đợi, nhưng giờ đây, bạn phải quản lý tính chất rỗng của View
bất cứ khi nào bạn tham chiếu đến thuộc tính này. Một giải pháp hay hơn là dùng lateinit
để khởi tạo View
, như trong ví dụ sau:
class LoginFragment : Fragment() {
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView.setText(R.string.auth_failed)
}
}
Từ khoá lateinit
cho phép bạn tránh khởi tạo thuộc tính khi
tạo một đối tượng. Nếu thuộc tính của bạn được tham chiếu trước khi được khởi tạo,
Kotlin sẽ gửi UninitializedPropertyAccessException
, vì vậy, hãy nhớ khởi tạo thuộc tính của bạn càng sớm càng tốt.