Thứ Hai, 25 tháng 1, 2021

Giới thiệu loạt bài hướng dẫn lập trình Kotlin Android đầy đủ nhất

 

Kotlin Bài 1: Giới thiệu loạt bài hướng dẫn lập trình Kotlin

Nguồn: cungdev.com

Chào mọi người, mình là Dương Vũ. Hôm nay mình sẽ viết bài đầu tiên trong loạt bài hướng dẫn lập trình Kotlin, sử dụng trong quá trình phát triển ứng dụng Android. Đây sẽ là loạt bài đầu tiên mình chia sẻ đến những ai yêu thích lập trình di động. Các kiến thức trong đây có thể sẽ mới mẻ, cũng có thể sẽ đơn giản đối với một số người đã trải qua. Vậy hãy cùng mình học tập, giúp đỡ và chia sẻ đến những người có cùng đam mê, sở thích về phát triển ứng dụng nhé. 

Tại sao lại có loạt bài này

Mình nhận thấy Kotlin là 1 ngôn ngữ cực kỳ hay và thú vị. Giữa hàng trăm hàng nghìn bài hướng dẫn về lập trình Android ngoài kia, mình chưa tìm ra 1 loạt bài nào giới thiệu bài bản, có lộ trình và dễ hiểu để cho người mới có thể nắm bắt. Có thể có những bài hướng dẫn về lập trình Android sử dụng Kotlin, nhưng rất ít bài hướng dẫn về những điều cốt lõi của ngôn ngữ này. Thế nên mình quyết định tạo ra loạt bài viết Hướng dẫn ngôn ngữ lập trình Kotlin. Loạt bài này sẽ đảm bảo cho bạn những kiến thức cốt lõi, quan trọng nhất để bạn có thể sử dụng trong phát triển ứng dụng Android nói riêng và các nền tảng khác nói chung. Và nó cũng hướng cho bạn cách học và cách đào sâu nghiên cứu, mở rộng cho ngôn ngữ này. So, let’s start!!!

Loạt bài này hướng đến ai

  • Nó hướng đến những người phát triển ứng dụng Android, muốn “chuyển giao công nghệ” từ Java sang Kotlin.
  • Hướng đến những bạn muốn học lập trình Android, mà chưa cả biết Java là gì. Yes!!! Các bạn có thể nhảy ngay vào Kotlin, hoàn toàn không có trở ngại gì cả.
  • Hướng đến … mình. Mình thực sự thích viết, đó là sở thích lớn của mình, chưa kể viết sẽ làm mình trau dồi thêm đc kha khá kiến thức, hehe :))

Nội dung của loạt bài hướng dẫn ngôn ngữ lập trình Kotlin

  • 15 bài hướng dẫn cover toàn bộ kiến thức cốt lõi của ngôn ngữ lập trình Kotlin, giúp bạn có đủ kiến thức để bay vào dự án thực tế 1 cách tự tin và dễ dàng. Vì chỉ có 15 bài thôi nên mỗi  bài sẽ có độ dài tương đối, các bạn hãy chịu khó theo dõi nhé.
  • Do bài viết hướng đến cả những người mới bắt đầu, nên đôi chỗ có thể giải thích hơi kỹ càng quá (đối với những người đã là dev có kinh nghiệm), nên chỗ nào các bạn thấy dễ quá, hay dài vl, thì xin các bạn đừng ném gạch, mà hãy next qua phần tiếp theo :)) Thanks
  • Loạt bài này được viết theo ngôn ngữ… đời sống. Tức là mình sẽ cố diễn giải vấn đề theo cách dân dã nhất, dễ hiểu nhất, chứ không sử dụng nhiều những từ ngữ mang tính học thuật, sẽ rất khó khăn cho ai mới tiếp xúc.
  • Phần sugguest, định hướng để các bạn tìm tòi thêm những kiến thức mới, nâng cao hơn, phục vụ cho dự án sau này.

 

Cách theo dõi loạt bài Kotlin sao cho hiệu quả nhất

Hãy cài IntelliJ IDEA

Như đã nói lúc đầu, bài viết này mình sẽ tập trung vào cốt lõi của ngôn ngữ Kotlin, và chỉ Kotlin mà thôi, không Android, không gì khác. Mình sẽ có riêng 1 loạt bài về hướng dẫn lập trình Android bằng Kotlin để vận dụng kiến thức thu được từ loạt bài này. Vì vậy ở trong những bài hướng dẫn của loạt bài này, mình sẽ không dùng Android Studio để hướng dẫn, mà thay vào đó sẽ dùng IntelliJ IDEA để code các demo. Tại sao? Bởi dùng Android Studio thì mình chỉ có thể tạo các dự án Android. Mà cứ mỗi lần muốn xem kết quả của 1 đoạn code, ta lại phải build nguyên 1 … con app! Rất mất thời gian.

Vì thế để đơn giản hóa mình sẽ sử dụng IntelliJ IDEA. IDE này cho phép ta tạo ra các project Kotlin. Đại khái là ta có thể dùng nó để xem kết quả của các đoạn code nhanh gọn đơn giản hơn, thay vì build nguyên 1 cái app. Các bạn hãy yên tâm vì nếu các bạn đã quen dùng AndroidStudio thì IntelliJ IDEA cũng có cách sử dụng hoàn toàn tương tự, giống đến 90%. Vì AS cũng build từ IntelliJ IDEA mà ra thôi. Nếu các bạn chưa biết cách cài đặt IDE này, hãy tham khảo bài viết Hướng dẫn cài đặt IntelliJ IDEA của mình nhé.

Hãy đọc thật kỹ 

Mình sẽ viết rất kỹ, rất kỹ về từng vấn đề dù là nhỏ nhất. Vì vậy mỗi bài viết có thể khá dài. Tuy nhiên  mình mong các bạn đừng bỏ qua chữ nào, bởi đằng sau mỗi đoạn text miên man đó là các kiến thức cốt lõi được diễn giải ra theo 1 cách dễ hiểu nhất (đối với những người mới). Nếu bạn đọc lướt qua, có thể bạn sẽ bỏ qua mất 1 phần kiến thức sâu hơn, nâng cao hơn mà mình muốn truyền đạt cho các bạn.

Hãy ứng dụng

Mặc dù đây mới chỉ là loạt bài giới thiệu về ngôn ngữ lập trình Kotlin, chúng ta sẽ không xây dựng app trong loạt bài này. Tuy nhiên mình muốn các bạn hãy lập tức ứng dụng những gì học được ngay khi đọc các bài viết. Hãy mở IDE lên và sẵn sàng gõ code. Chỉ có cách code thật nhiều mới có thể giúp chúng ta làm quen với 1 ngôn ngữ mới. Mình cũng xin báo trước rằng mình sẽ sử dụng rất, rất nhiều code để demo. Bởi theo mình nghĩ đó là cách đơn giản nhất giúp các bạn nhanh chóng hiểu được kiến thức. Các bạn cũng hãy tự xây dựng cho mình những đoạn code demo để ứng dụng kiến thức đã học nhé. Sẽ rất hiệu quả đấy!

Hãy chia sẻ

Hãy chia sẻ loạt bài đến những người mới bắt đầu. Những ai còn đang hoang mang giữa vô vàn kiến thức cả tiếng Anh lẫn tiếng Việt ngoài kia. Họ cần 1 lộ trình rõ ràng để làm mục tiêu học tập. Dù đã kiểm tra rất kỹ nhưng có thể ở đâu đó trong các bài viết, mình có sai sót. Hãy đọc và nếu thấy có sơ suất ở đâu, comment cho mình biết để mình cập nhật lại, để nội dung truyền tải được tốt hơn. Hãy cho đi, để được nhận lại.

Bắt đầu

Có thể các bạn sẽ tự hỏi rằng

  • Java ổn rồi ,cần thêm cái thứ Kotlin đó làm chi.
  • Thứ mới ra đó (Kotlin) chắc gì đã ổn định, Java thì best rồi, chả cần thiết phải mò vào Kotlin
  • Tao thích code Java, tao đã quen với nó, tao ghét tất cả mọi thứ khác!
  • Vân vân

Thì đúng, mình không phủ nhận những điều mà các bạn nghĩ ở trong đầu. Nhưng các bạn hãy thử nhìn vào những điểm tốt của Kotlin so với Java, rồi các bạn sẽ thấy, mình có lý do khi viết loạt bài hướng dẫn này.

Sự ngắn gọn

So với Java, Kotlin là vô cùng, vô cùng ngắn gọn. Bạn có thể hình dung, khi mà việc định nghĩa 1 class trong Java có thể tốn đến vài chục dòng hay cả trăm dòng code thì với Kotlin, ta chỉ tốn 1 dòng để làm điều tương tự. Mình sẽ ví dụ ngay đây. Sau đây sẽ là 1 class trong Java:

Trên đây là 1 class với 4 thuộc tính, hàm khởi tạo và các phương thức get, set cho các thuộc tính. Trong Kotlin nó sẽ như thế này:

Vỏn vẹn 1 dòng duy nhất! Đó chính là sự khác biệt về sự ngắn gọn của Kotlin. Đây chỉ là 1 ví dụ. Trong các dự án thực tế ta sẽ còn gặp hàng trăm tình huống mà nếu sử dụng Kotlin, số dòng code phải viết có thể giảm đi đến 90%. Rất tuyệt đúng không. (mình sẽ viết những loạt bài về xây dựng các ứng dụng với quy trình như trong thực tế)

An toàn hơn với các đối tượng null

Như các bạn đã biết thì NullPointerException là 1 lỗi hay gặp nhất ở trong các dự án viết bằng Java. Java cho phép chúng ta gán giá trị null đến 1 đối tượng, nhưng khi ta truy xuất đến đối tượng đó, lỗi NullPointerException sẽ được bắn ra. Mình đã từng làm 1 dự án Android, khi update version mới cho app, mình đã quên không cập nhật đối tượng Java theo config mới trên server. Và kết quả là trong 2 ngày đã có hơn 5000 log crash được báo về hệ thống, tất cả đều là NullPointerException gây ra, bạn có thể hiểu được cảm giác thốn đến thế nào, khi có đến 95% người dùng app bị crash :-s Yeah, và Kotlin được thiết kế để giảm thiểu tối đa sự xuất hiện của cái Exception khốn nạn này 😀 Cực an toàn và tiện lợi, mình sẽ có riêng 1 bài về vấn đề này, các bạn hãy đón chờ nghe.

Hoàn toàn tương thích với Java

Yes! Bạn có thể làm 1 dự án với cả Java và Kotlin cùng 1 lúc. Kotlin được thiết kế để giao tiếp với Java 1 cách hoàn hảo. Bạn có thể sử dụng Java class trong Kotlin và ngược lại, cũng có thể sử dụng Kotlin class trong Java. Tuyệt đúng ko? Điều này có nghĩa là Kotlin có thể sử dụng tất cả các thư viện và framework Java hiện có. Điều này cũng có nghĩa là nếu bạn thích, bạn có thể mở rộng, phát triển dự án Java cũ với Kotlin rất dễ dàng.

Trên đây chỉ là 3 trong số rất nhiều điều hay và thú vị khác của Kotlin mà Java không có. Hãy cùng mình tìm hiểu kỹ hơn trong các bài viết loạt bài này nhé.


Kotlin Bài 2: Biến và các kiểu dữ liệu cơ bản trong Kotlin

Xin chào, mình là Dương Vũ. Hôm nay mình sẽ trình bày về các kiểu dữ liệu cơ bản trong Kotlin. Sau bài này các bạn có thể biết cách khai báo biến, hằng số. Đồng thời các bạn cũng có kiến thức về các kiểu dữ liệu cơ bản trong Kotlin (mà thực ra là trong mọi ngôn ngữ lập trình). Các bạn cũng có kiến thức về mảng, danh sách, cách thức làm việc với chúng trong Kotlin. Yeah, bắt đầu nhé!

Biến và hằng số

Biến

Trong Kotlin, chúng ta dùng từ khoá var để khai báo 1 biến và dùng từ khoá val để khai báo 1 hằng. Biến có thể thay đổi được giá trị còn hằng số thì không. Ví dụ, ta có thể khai báo biến như sau:

Ở trên đây mình đã khai báo các biến myNamemyAgeisMeGay. Mỗi biến đều được khai báo kiểu dữ liệu và giá trị ban đầu của nó. Chắc các bạn cũng dễ dàng nhận thấy cú pháp khai báo biến sẽ là:

Cách khai báo biến này cũng tương tự với cách khai báo bên Java:

Tuy nhiên ở bên Kotlin có 1 sự tiện lợi hơn Java, đó là khi khai báo biến ta có thể không cần chỉ ra kiểu dữ liệu. Ví dụ như sau:

Có nghĩa là khi ta khai báo biến có giá trị khởi tạo, Kotlin sẽ tự động gán kiểu cho biến dựa vào giá trị khởi tạo đó.

Hằng số

Hằng số hoàn toàn tương tự như biến số. Chỉ khác là chúng không thể thay đổi giá trị như biến số. Hằng số được khai báo bởi từ khoá val:

Lưu ý: nếu bạn cố thay đổi giá trị của 1 hằng số, bạn sẽ nhận được thông báo sau: Val cannot be reassigned (hằng số không thể được gán lại gía trị). Ví dụ thế này

Bạn sẽ nhận ngay thông báo lỗi từ IDE. Hãy tự thử nghiệm thêm nhé!

Các kiểu dữ liệu cơ bản

Kiểu dữ liệu số

Trong Kotlin có nhiều kiểu dữ liệu để biểu diễn số. Nhưng về cơ bản cũng tương tự Java thôi. Kotlin cung cấp những kiểu dữ liệu số sau:

Các kiểu số nguyên

  • Long – 64 bit
  • Int – 32 bit
  • Short – 16 bit
  • Byte – 8 bit

Các kiểu số thực

  • Double – 64 bit
  • Float – 32 bit

Ví dụ:

Lưu ý: Khi khởi tạo 1 biến số thực, mặc định Kotlin sẽ hiểu biến đó có kiểu Double. Ví dụ:

Nếu muốn khởi tạo 1 biến dạng Float ta cần thêm ký tự f hoặc F đằng sau giá trị:

Tương tự với biến số nguyên, mặc định sẽ được hiểu là kiểu Int:

Nếu muốn đưa nó về dạng long, ta thêm ký tự L sau giá trị:

Chuyển đổi giữa các kiểu dữ liệu số

Tất cả các kiểu dữ liệu số đều được cung cấp các hàm để chuyển đổi từ kiểu dữ liệu này sang kiểu dữ liệu khác. Ví dụ:

Như vậy mình có thể convert 1 biến từ kiểu này sang kiểu kia 1 cách dễ dàng.

Lưu ý: Ta không thể trực tiếp gán giá trị của 2 biến khác kiểu như sau:

Kiểu dữ liệu Boolean

Kiểu Boolean trong Kotlin hoàn toàn giống với Java. Giá trị của kiểu dữ liệu này là true hoặc false. Các toán tử đối với kiểu Boolean cũng hoàn toàn giống Java:

  • || (hoặc)
  • && (và)
  • ! (phủ định)

Ví dụ:

Kiểu dữ liệu String

String biểu diễn dữ liệu dạng chuỗi ký tự. Trong Kotlin có 2 dạng String.

  • Dạng thông thường: các ký tự trong String đc biểu diễn trong cặp ngoặc kép, dạng này khá giống với String trong Java:

Khi muốn hiển thị các ký tự đặc biệt ,ví dụ như xuống dòng, ta phải dùng cách sử dụng ký hiệu. Ví dụ để tạo 1 string chứa 3 dòng ta phải làm như sau:

Lưu ý: Ở trên mình đã sử dụng câu lệnh println để in ra giá trị của chuỗi myString trên console. Hàm println có thể in ra giá trị của tất cả các biến, không riêng gì biến kiểu String.

Kết quả in ra ở console sẽ là:

Như mọi người thấy ta phải dùng \n để thay cho ký tự xuống dòng. Nhưng với dạng String thứ 2 thì khác. 

  • Dạng thứ 2: các ký tự trong String được biểu diễn trong cặp dấu nháy 3 “””. Ví dụ:

Kết quả:

Với dạng này ta có thể sử dụng xuống dòng, tab hay bất kỳ 1 ký tự đặc biệt nào 1 cách thoải mái. Cứ như kiểu ta đang soạn thảo văn bản vậy :3

  • String template

Nếu trong Java, để hiển thị giá trị của 1 biến số trong 1 chuỗi thì ta phải dùng phép cộng chuỗi hoặc String.format. Thì ở Kotlin, mọi việc đơn giản và trực quan hơn rất nhiều. String trong Kotlin cho phép ta gắn giá trị của 1 biến vào thân chuỗi chỉ với ký tự $. Ví dụ:

Thế là giá trị của các biến numberOfApples ,numberOfBananasnumberOfOrange được gắn vào String cực kỳ gọn nhẹ. Ví dụ 2:

Ví dụ này cho thấy, khi muốn biểu diễn giá trị của 1 biểu thức (hoặc là hàm số) thì biểu thức (hàm số) đó phải được bao bởi cặp {}. Như ví dụ trên mình đã dùng ${numberOfBlueCar+numberOfRedCarđể biểu diễn tổng của 2 biến numberOfBlueCar và numberOfRedCar đó. Ví dụ nữa :

Kiểu dữ liệu mảng

Mảng (Array) là 1 tập hợp các phần tử. Trong Kotlin có 2 cách để tạo ra mảng, đó là sử dụng phương thức

hoặc dùng hàm khởi tạo:

Ví dụ, ta thử tạo 1 mảng chứa các số nguyên và số thực sử dụng phương thức arrayOf()

Ta vừa khởi tạo 1 mảng có số phần tử là 7 và các phần tử như trên. Nếu muốn tất cả các phần tử của mảng phải có cùng kiểu dữ liệu, ta cần khai báo kiểu dữ liệu bằng cách sau:

Như ở ví dụ trên ta đã khai báo kiểu Int cho array cần khởi tạo. Có 1 cách khác như thế này:

Kotlin cũng cung cấp cho ta các hàm để tạo mảng chứa kiểu dữ liệu cơ bản khác:

Giờ ta sẽ thử dùng hàm khởi tạo Array() để tạo ra 1 mảng. Hàm này yêu cầu truyền vào size (số phần tử) và 1 lambda function. Mình sẽ tìm hiểu về lambda function trong những bài viết sau, giờ ta chỉ cần hiểu đơn giản đó là 1 hàm không có tên, và thực hiện thao tác với tham số đầu vào là chỉ số của mảng:

Trong đoạn code trên, ta đã truyền vào size của mảng là 4. Và 1 lambda function sử dụng tham số đầu vào là chỉ số của mảng để tính toán giá trị của phần tử ứng với chỉ số đó (giá trị bằng 3 lần chỉ số). Mảng numbersArray trên có cùng giá trị với mảng:

Đơn giản đúng không?

Comment – Chú thích

Comment là những đoạn code không được biên dịch, có vai trò ghi chú, chú thích trong code. Comment trong Kotlin giống hệt với Java:

Kết luận

Ở bài này mình đã giới thiệu về biến, hằng số, các kiểu dữ liệu cơ bản của Kotlin, mảng, comment. Bài sau mình sẽ giới thiệu 1 kiến thức cực kỳ thú vị (và quan trọng) trong lập trình Kotlin. Đó là cách kiểm tra biến null an toàn trong Kotlin. Các bạn hãy đọc và cho ý kiến nhé.


Kotlin Bài 3: Null Safety – Kiểm tra biến null an toàn trong Kotlin

Chắc hẳn nếu bạn đã code qua Java thì bạn đã quá quen thuộc với dòng thông báo lỗi NullPointerException. Đây cũng là 1 lỗi hết sức phổ biến trong nhiều ngôn ngữ lập trình. Nó xuất hiện khi ta truy xuất đến 1 trường hoặc 1 phương thức của 1 biến hay đối tượng null (tức là biến hay đối tượng đó không có bất cứ 1 giá trị nào). Và Kotlin được thiết kế để giảm thiểu tối đa sự xuất hiện của sai lầm tỷ đô này. Hôm nay mình sẽ viết bài trình bày về cách kiểm tra biến null an toàn trong Kotlin. Cùng xem xem có gì hay ho ở đây nhé!

Non-null types – Kiểu dữ liệu không thể null và Nullable types – Kiểu dữ liệu có thể null

Non-null types – Kiểu dữ liệu không thể null

Như ở bài trước, mình đã giới thiệu về các kiểu dữ liệu và cách khai báo biến trong Kotlin, ta đã biết cách khai báo biến kèm theo giá trị khởi tạo:

Giờ ta thử gán cho biến name ở trên giá trị null xem sao:

Ta sẽ nhận đc ngay 1 thông báo lỗi Null cannot be a value of a non-null type String. Tức là giá trị null không thể được gán cho 1 biến có kiểu non-null type String. Ở bài trước, mình đã trình bày về các kiểu dữ liệu cơ bản. Tuy nhiên các kiểu dữ liệu đó đều là các kiểu non-null type, có nghĩa là không chấp nhận giá trị null. Ok, rắc rối thật đấy, không cho ta gán giá trị null thì ta thử không gán giá trị gì xem nào 😀

Giờ thì lại nhận đc thông báo thế này: variable ‘name’ must be initialized, tức là biến name phải được gán giá trị.

Vậy là bằng mọi giá, Kotlin ngăn cản ta định nghĩa 1 biến với giá trị null. Điều này có vẻ bất tiện nhưng thực ra rất an toàn, bởi khi ta không có biến null, đồng nghĩa với việc sẽ không dính vào lỗi NullPointerException đáng ghét. 1 tính năng rất cool mà Java không có đúng ko.

Nhưng chờ đã, trong thực tế, đâu phải mọi thứ đều nhất thiết phải có giá trị (khác null) đúng không. Đôi khi vẫn có những biến phải gán cho giá trị null (để giải phóng bộ nhớ chẳng hạn), hoặc những biến không phải trong trường hợp nào cũng truy xuất đến, chúng không nhất thiết phải có giá trị. Vậy để gán giá trị null cho 1 biến ta làm thế nào? Khi đó ta sẽ cần đên Nullable types – Kiểu dữ liệu có thể null.

Nullable types – Kiểu dữ liệu có thể null

Ta khai báo như sau:

Ta đã thêm 1 dấu ? vào sau kiểu dữ liệu String. Điều đó tức là biến name vẫn có kiểu là String, nhưng lúc này nó đã được phép mang giá trị null. Và lúc này trình biên dịch không còn báo lỗi nữa. Ta chạy chương trình và thu được kết quả  name = null hiển thị ở console. Và String? chính là 1 Nullable type, tức là các biến có kiểu này được phép mang giá trị null.

Vậy đúc kết lại, để khai báo các biến có thể mang giá trị null, ta dùng các kiểu dữ liệu Nullable type với cú pháp:

Safe call – Lời gọi an toàn

Đối với biến được khai báo với kiểu  Nullable type, khi truy xuất đến chúng, ta cần phải sử dụng Safe call – Lời gọi an toàn để đảm bảo chương trình ko xảy ra lỗi. Ví dụ ta muốn lấy độ dài của chuỗi name bên trên:

Ký tự ? được dùng để kiểm tra biến name có null hay không. Nếu name có giá trị,  ta lấy được giá trị của name.length và gán vào biến lengthOfName. Ngược lại, name.length sẽ không được truy xuất đến và biến lengthOfName sẽ có giá trị null. Lời gọi đến name?.length bên trên gọi là 1 safe call (lời gọi an toàn) vì nó sẽ không bao giờ gây lỗi NullPointerException, dù cho biến name có mang giá trị null. Kết quả của đoạn code trên sẽ là:

length = null

Thử gán name = “Nam” và chạy chương trình:

Ta sẽ thu được kết quả: length = 3

Nếu bỏ dấu ? trong lời gọi đến name?.length, trình biến dịch sẽ báo lỗi: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String. Tức là đối với các kiểu dữ liệu nullable (cho phép null), ta chỉ có thể dùng safe call như mình giới thiệu bên trên, hoặc sử dụng ký hiệu !! 

Vậy !! là cái gì? 

Toán tử !!

Nếu bạn chắc chắn biến name là khác null, có thể dùng !! để thay cho ?. Khi dùng !! thì dù cho name có null hay không, lời gọi đến name.length vẫn sẽ được gọi. Ta sẽ thử:

Kết quả: Length = 3

Nhưng khi name bị null thì sao:

Lúc này ta sẽ nhận được thông báo: Exception in thread “main” kotlin.KotlinNullPointerException. Đây chính là điều ta muốn tránh.

Mình không khuyến khích sử dụng !! vì nếu ở ví dụ trên, name mang giá trị null, ngoại lệ NullPointerException sẽ bị văng ra. Đôi khi ta chắc chắn 1 biến là not null nhưng vì lý do nào đó nó bị mang giá trị null, khi đó sử dụng !! sẽ rất nguy hiểm.

Thêm 1 cách để kiểm tra biến null an toàn

Như ở trên mình đã dùng ?.let để xác định xem biến name có null hay không, nếu ko null thì các câu lệnh trong khối lệnh {} mới được thực hiện. Và trong khối lệnh ta không cần phải thêm ký tự safe call (?) hay null-aserted (!!) để truy xuất đến giá trị name.length trong đối tượng name. Đơn giản vì nó đã được chứng nhận là an toàn sau từ khoá let.

Toán tử Elvis

Khi ta có 1 đối tượng str có thể mang giá trị null. Ta có thể sử dụng toán tử Elvis để kiểm tra giá trị của nó. Cách sử dụng như sau:

Câu lệnh trên có ý nghĩa là: Nếu biểu thức bên trái dấu ?: (trong trường hợp này là str?.length) khác null, thì biến length sẽ được gán giá trị của str?.length, ngược lại, biểu thức bên trái dấu ?: mang giá trị null, biến length sẽ được gán giá trị của biểu thức bên phải dấu ? (trong trường hợp này là -1). Chỉ đơn giản là vậy.

Tổng kết

Như vậy trong bài này mình đã giới thiệu với các bạn cách sử dụng safe call (lời gọi an toàn) đối với các đối tượng có kiểu nullable-type. Việc làm kiểm tra biến null này sẽ giúp các bạn giảm thiểu rất nhiều nguy cơ bị dính ngoại lệ NullPointerException, cũng tức là giảm tỉ lệ bị crash app khi bạn phát triển ứng dụng Android sau này.



Kotlin Bài 4: Các cấu trúc vòng lặp và điều kiện trong Kotlin

Xin chào, Mình là Dương Vũ, ở bài viết trước mình đã nói về cách truy xuất biến một cách an toàn. Bài này mình sẽ trình bày về các cấu trúc vòng lặp và điều kiện trong Kotlin. Vòng lặp và điều kiện là những tính năng không thể thiếu đối với 1 ngôn ngữ lập trình hiện đại. Và Kotlin còn làm cho chúng trở nên vô cùng thú vị. Cùng tìm hiểu nhé.

Các cấu trúc điều kiện

Cấu trúc if và if else

Cấu trúc if kiểm tra tính thoả mãn của 1 điều kiện và thực hiện một (chuỗi) hành động nếu điều kiện đó thoả mãn. Nếu bạn đã từng làm việc với java, bạn sẽ thấy cấu trúc if và if else của Kotlin sử dụng giống hệt như Java. Đây là 1 ví dụ với việc sử dụng if:

bởi điều kiện của cấu trúc if (age>18)  thoả mãn, Màn hình sẽ hiển thị dòng log You are adult. 1 ví dụ với if – else:

Màn hình sẽ hiển thị ra dòng log 10<20. 

Tuy nhiên ngoài cách sử dụng truyền thống ra, ta có thể dùng cấu trúc if như 1 biểu thức, tức là biểu thức if cũng trả về giá trị, sau đây là ví dụ:

Ta thấy trên đây là bài toán tìm max của 2 số rất đơn giản. Cái hay là trong Kotlin, cấu trúc if có thể return về giá trị và giá trị đó có thể được gán cho 1 biến. Như ở ví dụ trên nếu firstNumber > secondNumber thì cấu trúc if sẽ return về giá trị firstNumber, ngược lại sẽ return về giá trị secondNumber. Chạy đoạn code trên ta sẽ thu được

 

Lưu ý: nếu bạn sử dụng if dưới dạng 1 biểu thức, thì cấu trúc if bắt buộc phải có else.

Cấu trúc when

Cấu trúc when tương tự như switch – case của Java hay C. Đây cũng là 1 cấu trúc điều kiện mình rất hay sử dụng. Nó có dạng như thế này

Cấu trúc when kiểm tra tham số đầu vào (ở trường hợp trên là number) với các điều kiện trong thân cấu trúc. Khi bắt gặp điều kiện đúng, các câu lệnh tương ứng với điều kiện đó sẽ được thực hiện và các điều kiện khác sẽ không được xét đến nữa.

Nếu như trong java, các nhánh điều kiện trong cấu trúc switch – case chỉ có thể là hằng số, thì cấu trúc when trong Kotlin tỏ ra mềm dẻo hơn với việc cho phép các điều kiện có thể là các biểu thức, hoặc hàm. Ví dụ:

Ví dụ trên kiểm tra xem numberOne có phải là 1 nửa của numberTwo hay không. Ví dụ có vẻ ngu ngốc nhưng mô tả rõ ràng việc sử dụng biến trong các nhánh điều kiện của when :)) Khi chạy đoạn code trên ta thu đc dòng log: 19 != 21 /2.

Nếu ta thay giá trị của  numberTwo = 38 thì ta sẽ thấy log 19 = 38 /2.

Vậy ta có cấu trúc tổng quát của when là thế này

Lưu ý: nếu chỉ có 1 dòng lệnh thì ta có thể không dùng cặp { }

Vòng lặp

Vòng lặp được sinh ra để thực hiện lặp đi lặp lại 1 công việc nào đó. Kotlin cung cấp cho ta 3 dạng vòng lặp là while, do … while và for. Mình sẽ tìm hiểu từng loại 1 nhé.

Vòng lặp while

Đây là 1 dạng vòng lặp có điều kiện. Vòng lặp sẽ chạy chừng nào điều kiện đó vẫn còn đang đúng. Nếu ai đã code Java thì sẽ thấy vòng lặp while trong Kotlin không khác gì Java cả. Ví dụ:

Trên đây là ví dụ về vòng đời của 1 người. Vòng lặp sẽ chạy chừng nào điều kiện age<100 còn thỏa mãn. Và các bạn thấy khi run đoạn lệnh trên console sẽ hiển thị:

Vòng lặp do … while

Đây là cách viết khác của ví dụ vòng đời trên sử dụng vòng lặp do … while. Vẫn là sống từ 1 đến 100 tuổi rồi chết. Vòng lặp do … while thực tế cũng gần giống như vòng lặp while. Cũng là dạng vòng lặp có điều kiện. Chỉ khác 1 điều là, ở vòng chạy đầu tiên thì điều kiện sẽ không được xem xét. Có nghĩa là với vòng lặp do … while, sẽ có ít nhất 1 vòng được thực thi. Để thấy rõ, bạn hãy thử đặt giá trị khởi tạo của biến age là 101. Khi đó dù age không thoả mãn điều kiện (age <= 100) nhưng vẫn sẽ có 1 câu lệnh được in ra: Now i am 101 years old.

Vòng lặp for

Vòng lặp for duyệt qua tất cả các phần tử trong 1 mảng (array) hoặc danh sách (list) và xử lý giá trị ứng với mỗi phần tử. Ví dụ:

Ok, giờ ta muốn mỗi vòng lặp nhảy 2 đơn vị thì sao? Ta sẽ làm thế này:

Đơn giản là thêm từ khoá step và cuối điều kiện lặp.

Thế nếu ta muốn duyệt từ 5 trở về 1 thì sao? Ta có từ khoá downto giúp ta làm việc đó:

Và duyệt từ 5 trở về 1, mỗi vòng lặp nhảy 2 đơn vị:

Sử dụng vòng for để duyệt qua các phần tử của 1 mảng (Array):

Ví dụ trên khởi tạo 1 list các số nguyên và duyệt qua list đó, in ra console tính chẵn lẻ của từng phần tử. Đơn giản đến mức ko thể giải thích rõ hơn, đúng ko? Thêm 1 ví dụ nữa nhé:

Ví dụ trên khởi tạo 1 mảng các tên cầu thủ, sau đó sử dụng vòng lặp để duyệt qua tất cả các phần tử trong mảng đó, phần tử nào bắt đầu bằng ký tự R thì sẽ log ra màn hình. Kết quả:

Ngoài cách trên ra ta cũng có thể duyệt mảng bằng cách khác, đó là duyệt qua chỉ số:

Đây là bài toán đơn giản tính tổng 1 mảng. Nhưng ở ví dụ này ta duyệt mảng thông qua chỉ số (thứ tự) chứ ko duyệt trực tiếp qua các phần tử của listNumber. Bản chất của việc này là ta tạo ra 1 list chỉ số: 0..listNumber.size – 1 gồm các giá trị từ 0 đến listNumber.size – 1Sau đó ta duyệt list này để lấy các chỉ số (index) của  listNumber và truy xuất các phần tử của listNumber thông qua index, phục vụ cho việc tính tổng.

Break và continue

Lưu ý: break và continue trong Kotlin giống hệt như trong java, anh em nào biết rồi thì có thể bỏ qua không đọc :))

break và continue là 2 từ khóa để tạo ra các bước nhảy trong vòng lặp. Cụ thể:

break dùng để thoát ra khỏi vòng lặp.

continue dùng để lập tức chuyển tới vòng lặp tiếp theo, bỏ qua các câu lệnh còn lại của vòng lặp hiện tại.

break và continue có thể dùng được trong mọi thể loại vòng lặp, dù có là for hay while hay là do – while vẫn ok tất. Ví dụ về break

Vẫn là ví dụ về vòng đời, nhưng ở đây có chút khác biệt. Khi age có giá trị là 25, thanh niên console đi xe máy ra ngoài đường và ngắm gái, kết quả là đâm vào cây và hẹo. Thế nên ta dùng lệnh break để thoát khỏi vòng lặp for(age in 1..100)  dù age mới có giá trị là 25. Kết quả hiện ra trên màn hình

Ví dụ về continue:

Đây là 1 ví dụ về 1 anh cày game, ở mỗi level của anh ấy đều có 1 dòng log println(“My level is $level”), tuy nhiên khi đang ở level 3 thì anh ấy nhận được gift code khủng nên nhảy 1 phát lên level 9: level += 6 và bỏ qua dòng log println(“My level is $level”) ở vòng lặp khi level = 3. Điều này có được nhờ câu lệnh continue, nó sẽ bỏ qua tất cả những câu lệnh ở phía sau nó (trong vòng lặp) và lập tức chuyển sang vòng lặp tiếp theo.

Ví dụ khác:

Tổng kết

Như vậy, cơ bản mình đã trình bày xong về Các cấu trúc vòng lặp và điều kiện trong Kotlin.

Đến đây ta đã có những trang bị kiến thức:

  • Các kiểu dữ liệu cơ bản
  • Các kiểu dữ liệu mảng, danh sách
  • Kiểu dữ liệu nullable, các cách sử dụng biến an toàn (tránh NullPoiterException)
  • Cấu trúc điều kiện if, if-else, when
  • Các dạng vòng lặp forwhile
  • Các cách ngắt vòng lặp sử dụng breakcontinue

Đây là các thành phần cơ bản nhất mà ngôn ngữ nào cũng phải cung cấp. Đối với ai đã từng làm với Java chắc ko có khó khăn gì. Bài tiếp theo sẽ bắt đầu trình bày về các kiến thức liên quan đến cách sử dụng hàm trong Kotlin. Và những sự ưu việt của Kotlin so với Java (tất nhiên là về cú pháp). Rất mong các bạn sẽ ủng hộ mình.



Kotlin Bài 5: Sử dụng hàm trong Kotlin

Ở bài trước mình đã trình bày về các cấu trúc điều kiện và vòng lặp trong Kotlin. Bài hôm nay mình sẽ giới thiệu về cách định nghĩa và sử dụng hàm trong Kotlin, sau đó là nói về cách tổ chức code sử dụng package.

Hàm (Function)

Hàm là 1 tập hợp các đoạn câu lệnh để thực hiện công việc nào đó. Hàm có thể có tên và cũng có thể không. Việc sử dụng hàm sẽ khiến cho code của ta ngắn gọn hơn và không bị lặp đi lặp lại các đoạn code.

Khai báo và gọi hàm

Trong Kotlin, ta định nghĩa hàm sử dụng từ khóa fun:

Và thử gọi hàm qua câu lệnh:

Trong ví dụ trên, ta đã khai báo hàm helloMessage với 1 tham số đầu vào là name có kiểu String. Hàm này trả về kiểu dữ liệu String. Cách khai báo tham số khi định nghĩa hàm là:

Lưu ý: Hàm có thể có hoặc không có tham số.

Ta xem thử 1 ví dụ khác:

Và gọi hàm sayHello:

Ta thấy là hàm sayHello ở trên cũng tương tự với hàm helloMessage ta viết lúc đầu. Chỉ khác 1 điều là hàm sayHello trả về kiểu dữ liệu Unit (thay vì String) và tự nó print ra câu chào. Bạn có thể thấy hàm sayHello không có câu lệnh return ở cuối hàm. Bởi đơn giản, kiểu dữ liệu Unit là kiểu dữ liệu đặc biệt, khi hàm trả về kiểu dữ liệu này, ta không bắt buộc phải có câu lệnh return để trả về kết quả cho hàm. Unit trong Kotlin giống như Void trong C và Java vậy. Và trong Kotlin, khi bạn định nghĩa 1 hàm mà không khai báo kiểu dữ liệu trả về, thì tức là hàm của bạn đã trả về kiểu Unit:

Định nghĩa giá trị mặc định của tham số:

Ta viết lại hàm sayHello như thế này:

Như ta thấy, so với hàm đã viết ở phần 1, tham số đầu vào name có thêm định nghĩa “Dương vũ” điều này tức là “Dương Vũ” là giá trị mặc định của tham số name. Nếu ta gọi hàm sayHello mà không truyền tham số name, Kotlin sẽ sử dụng giá trị mặc định của tham số name đó. Thử gọi hàm:

Còn tất nhiên khi ta truyền giá trị cho tham số name thì giá trị mặc định sẽ không được sử dụng nữa:

Việc Kotlin cho phép định nghĩa giá trị mặc định của tham số đã giúp cho ta giảm thiểu số lượng hàm phải viết. Nếu như trong Java, không có tính năng này, ta đã phải viết 2 hàm sayHello(name) và sayHello() để xử lý các trường hợp có và không có tham số đầu vào rồi. Rất tiện đúng không?

Sử dụng tên cho tham số

Sử dụng tên cho tham số là gì và vì sao trong một số trường hợp bắt buộc phải cần đến nó. Hãy cùng mình xem 1 ví dụ. Ta sẽ định nghĩa 1 hàm như sau:

Ở trên ta khai báo 1 hàm với 3 tham số đầu vào: firstNamemiddleNamelastName. Trong đó middleName có giá trị mặc định là Van. Như mình đã nói ở phần trên, nếu không truyền tham số middleName thì Kotlin sẽ sử dụng giá trị mặc định của nó (là Van) đúng không? Giờ ta thử gọi hàm mà không truyền middleName vào xem sao:

Lúc này, ta sẽ nhận được thông báo lỗi: No value passed for parameter lastName (chưa truyền tham số lastName). Vì sao? Vì khi này, Kotlin hiểu rằng tham số thứ 2 (có giá trị “Nam“) là middleName, chứ không phải là lastName như ta mong muốn. Khi này ta cần phải làm như sau:

Lúc này ta đã chỉ rõ “Nam” là giá trị của tham số lastName chứ không phải của middleName, và middleName do không truyền vào nên sử dụng giá trị mặc định.

Lưu ý: Việc truyền tên tham số trong lúc gọi hàm đôi khi không bắt buộc, nhưng nó làm cho code của ta dễ đọc hơn, vì vậy mình khuyến khích khi sử dụng hàm, hãy cố gắng truyền thêm tên tham số vào, để sau này đọc lại code không bị vất vả.

Hàm với 1 dòng lệnh duy nhất – Single line function

Khi giá trị trả về của 1 hàm có thể viết được bằng 1 dòng lệnh duy nhất, ta có thể bỏ cặp {} và từ khóa return để đơn giản hóa. Ví dụ:

Hàm trên tính tổng của 2 số, và ta chỉ cần duy nhất 1 dòng để định nghĩa hàm, ko cần đến {} và từ khóa return, rất ngắn gọn.

Thêm 1 ví dụ nữa:

Hàm không giới hạn tham số

Đôi khi ta không biết trước số lượng tham số cần truyền vào cho hàm. Ta có thể cần truyền 1, 2 hay thậm chí 100 tham số. Lúc này ta cần định nghĩa 1 hàm không giới hạn tham số. Ví dụ như sau:

Như các bạn thấy ta đã thêm từ khóa vararg vào trước tham số numbers (kiểu Int). Có nghĩa là ở đây ta có thể truyền bao nhiêu tham số (kiểu Int) tùy thích. Và hàm getSum này sẽ tính tổng của tất cả các tham số truyền vào. Thử gọi hàm:

Hàm cục bộ

Kotlin cho phép ta định nghĩa hàm trong thân 1 hàm khác.

Vẫn là hàm in ra 1 câu chào, đầu vào là firstName  lastNameNhưng lần này, trong thân hàm  printGreeting ta đã định nghĩa thêm 1 hàm khác, getFullName trả về tên đầy đủ dựa vào 2 tham số đầu vào firstName  lastName của hàm printGreeting Và hàm printGreeting sẽ sử dụng giá trị trả về từ hàm getFullNameThử gọi hàm:

Lưu ý: Hàm cục bộ chỉ có thể sử dụng được trong thân hàm mà nó được định nghĩa.

Như vậy là trên đây mình đã giới thiệu cơ bản về khai báo hàm và cách sử dụng hàm trong nhiều trường hợp. Vẫn còn 1 số kiến thức khác nâng cao hơn như lambda function, extension function, … mình sẽ trình bày trong những bài sau, trong những tình huống phù hợp hơn. Còn bây giờ, ta chỉ cần biết được cơ bản về hàm trong Kotlin, vậy là đủ nhỉ ;).

Packages

Nếu bạn từng làm việc với Java, chắc hẳn bạn đã biết rằng Java sử dụng các gói (package) để chứa các lớp có liên quan với nhau. Ví dụ như package java.util chứa các lớp tiện ích mà ta thường sử dụng như DateHashMapCollections, … Ở trong Kotlin, package cũng có khái niệm tương tự. Tuy nhiên package trong Kotlin không chỉ chứa được các lớp mà nó còn có thể chứa các hàm (lớp là gì thì mình sẽ trình bày ngay bài sau). Và việc định nghĩa package ở trong Kotlin cũng cực kỳ dễ dàng. Trong Kotlin, các package được định nghĩa với từ khóa package.

Định nghĩa package

Giờ mình sẽ hướng dẫn các bạn cách định nghĩa, khai báo 1 package. Các bạn hãy tạo 1 project mới trong IntelliJ IDEA. Tạo 1 file Kotlin đặt tên là Utils.kt:

Tạo file Utils.kt

Note: Nếu bạn còn đang thắc mắc về cách tạo project, tạo file/class thì hãy đọc lại bài hướng dẫn sử dụng IntelliJ IDEA của mình nhé.

Giờ mình sẽ định nghĩa packge Utils như sau:

Quá đơn giản đúng không? Vậy là ta đã định nghĩa xong package Utils bằng từ khóa package rồi. Giờ, bên trong file Utils.kt, ta sẽ định nghĩa thêm 1 lớp MyClass nữa (ta sẽ tìm hiểu về lớp trong các bài viết sau):

Việc làm trên có nghĩa là ta đã định nghĩa lớp MyClass nằm trong package Utils. Và tên đầy đủ  (fully qualified name) của lớp MyClass là: Utils.MyClass. Vậy tên đầy đủ của 1 class nằm trong 1 package sẽ là

Giờ ta sẽ thử định nghĩa 1 hàm trong package Utils như sau:

Cũng tương tự như lớp MyClass ta vừa tạo ở trên, hàm getPi sẽ có tên đầy đủ là Utils.getPi()Tên đầy đủ dùng để làm gì, mình sẽ trình bày ngay ở phần dưới.

Import package

Trong Kotlin, để sử dụng các lớp, hàm, … nói chung là mọi thứ trong package, ta cần phải import package đó. Để làm việc này, ta sử dụng từ khóa import. Bây giờ ta sẽ thử sử dụng package Utils mà ta đã định nghĩa ở trên. Ta sẽ tạo 1 file mới trong project, đặt tên cho nó là Main.kt. Ta viết sẵn cho file Main.kt này 1 hàm main như sau để chuẩn bị cho việc viết code:

Rồi Ok, giờ ta sẽ import package Utils đã viết trước đó. Thêm dòng sau vào ngay đầu file Main.kt:

Câu lệnh này đã import toàn bộ package Utils vào file Main.kt. Giờ ta có thể sử dụng bất cứ thứ gì có trong package Utils. Trong hàm main của file Main.kt, Ta sẽ thử gọi hàm getPi() của package Utils:

Lưu ý: nếu ta không muốn sử dụng toàn bộ package, mà chỉ muốn sử dụng 1 hàm (hoặc lớp) nào đó trong package, ta có thể import riêng hàm (lớp) đó:

Như vậy là ta đã sử dụng được hàm getPi trong package Utils. Giờ ta sẽ thử xóa dòng lệnh import bên trên đi xem sao. Ngay lập tức sẽ nhận được thông báo lỗi: Unresolved reference: getPi (không nhận biết được hàm getPi). Như vậy là nếu không import package Utils, ta sẽ không sử dụng được các thành phần của nó.

Sử dụng tên đầy đủ (fully qualified name) của các thành phần trong package

Nếu bạn không muốn import 1 package mà vẫn muốn sử dụng các thành phần của nó (vì dụ như hàm, lớp…), bạn cần phải sử dụng tên đầy đủ cùa thành phần đó:

Đây chính là vai trò của tên đầy đủ mà mình đã trình bày ở phần định nghĩa package. Nó giúp chúng ta không cần phải import 1 package mà vẫn sử dụng được các thành phần trong package đó. Tên đầy đủ của 1 hàm, lớp còn rất quan trọng trong trường hợp sau: Khi ta sử dụng nhiều package mà các package đó có các hàm trùng tên nhau.

Mình tạo ra 1 package khác có tên là Utils2, và cùng định nghĩa hàm getPi cho package Utils2 đó:

Giờ trong file Main.kt, ta vừa muốn sử dụng hàm getPi trong package Utils, vừa muốn sử dụng getPi trong package Utils2, lúc này ta cũng cần tới tên đầy đủ:

Tổng kết

Như vậy bài này mình đã trình bày xong cơ bản về cách khai báo và sử dụng hàm trong Kotlin. Mình cũng đã nói xong về vấn để sử dụng package. Tất nhiên những thứ trên đây không phải là tất cả về hàm hay package trong Kotlin, ta còn rất nhiều thứ nâng cao hơn, mới mẻ hơn. Mình sẽ trình bày trong các bài tiếp theo. Hãy theo dõi nhé.



Kotlin Bài 6: Sử dụng hàm trong Kotlin (nâng cao)

Xin chào, mình là Vũ. Ở bài viết trước mình đã giới thiệu cơ bản về cách khai báo và sử dụng hàm trong Kotlin. Trong bài này mình sẽ trình bày các kiến thức nâng cao hơn (và thú vị hơn) về hàm trong Kotlin. Chúng là top-level function, lambda function, là extension function, và còn nhiều nhiều nữa. Hãy cùng theo dõi nhé.

Trước khi bắt đầu, hãy tạo cho mình 1 project mới và tạo 1 file Main.kt với hàm mainMain.kt sẽ là nơi để ta viết các đoạn code demo. Nếu bạn chưa biết làm những điều này, vui lòng tham khảo lại bài viết hướng dẫn sử dụng IntelleJ IDEA của mình.

Top-level function

Top-level function là các hàm không nằm trong bất kỳ 1 lớp (class) nào. Chúng được định nghĩa trong các package và được sử dụng bằng cách gọi trực tiếp qua tên đầy đủ (trong trường hợp không import package) hoặc tên hàm (trong trường hợp đã import package). Nếu bạn từng làm việc với Java chắc đều đã quen thuộc với các hàm static nằm trong lớp Utils. Top-level function trong Kotlin hoàn toàn tương tự như static function trong Java.

Hàm getPi ở bài trước mình định nghĩa chính là 1 top-level function. Giờ để hiểu rõ thêm sẽ làm thêm 1 ví dụ khác nữa. Ta tạo 1 file đặt tên là Utility.kt, định nghĩa package com.duongvu.utils và khai báo 1 hàm getCurrentDate trong package đó:

Lưu ý: tên packge không nhất thiết phải giống tên file chứa nó. Như ở trên mình đã định nghĩa package com.duongvu.utils trong file Utility.kt, điều này hoàn toàn OK.

Lưu ý: Bạn chưa cần phải hiểu rõ từng dòng trong hàm getCurrentDate ở trên, chỉ cần hiểu nó trả về ngày hiện tại dưới dạng 1 String. Và hàm này sử dụng các lớp java.util.Date và java.text.SimpleDateFormat, nên ta cần import 2 lớp trên:

Giờ ta đã có thể sử dụng hàm getCurrentDate trong file Main.kt như sau:

hoặc:

hoặc:

Đó là tất cả các cách sử dụng 1 top-level function.

Lambda function

Lambda function là gì? Cấu trúc của lambda function?

Lambda function là các hàm không có tên. Chúng thường được sử dụng như các tham số để truyền vào 1 hàm khác (mình sẽ trình bày ở phần dưới). Lambda function còn có thể được biểu diễn dưới dạng các biến. Để rõ hơn hãy nhìn vào ví dụ của mình, trong file Main.kt mình sẽ khai báo 1 lambda function như thế này:

Ở trên mình đã định nghĩa 1 lambda function bởi 1 cặp {} và gán lambda function này vào biến message. Như các bạn thấy hàm này không cần định nghĩa bởi từ khóa fun, nó không hề có tên, cũng không hề có kiểu trả về. Trong hàm main, ta sẽ sử dụng nó như sau:

Giờ ta sẽ nâng cấp lambda function trên 1 chút, ta sẽ truyền thêm tham số cho nó:

Gọi hàm:

Như vậy, nếu lambda function có tham số đầu vào thì các tham số sẽ được khai báo như đối với hàm bình thường, sau đó là ký tự -> và kế đến là thân hàm.

Higher-order functions – Sử dụng lambda function như 1 tham số đầu vào

Một điều tuyệt vời mà Kotlin cho phép ta làm đó là có thể sử dụng hàm như 1 tham số. Đây là điều không thể làm được trong Java. Ta làm điều này như thế nào? Hãy cũng xem ví dụ:

Ở đây ta định nghĩa 1 hàm in ra tổng của 2 số nguyên. Hãy nhìn vào các tham số đầu vào của hàm printSummary này. 2 tham số đầu tiên là 2 số nguyên đầu vào để tính tổng, cái này quá dễ. Còn tham số cuối cùng:summaryFunction là Hàm thực hiện việc tính tổng của 2 tham số đầu tiên. Hàm printSummary cần 1 tham số đầu vào là 1 hàm có đặc điểm:

  • Nhận 2 tham số Int là tham số đầu vào
  • Trả về kiểu dữ liệu Int

printSummary chỉ đơn thuần sử dụng giá trị trả về của summaryFunction. Giờ ta thử sử dụng hàm printSummary:

Ở đây khi gọi hàm printSummary mình đã truyền giá trị các tham số kèm theo tên để các bạn dễ hiểu. Hãy để ý vào tham số thứ 3, mình đã sử dụng 1 lambda function nhận 2 giá trị đầu vào là a và b, và trả về giá trị a+b. Hàm này thỏa mãn điều kiện là nhận 2 tham số Int và trả về kiểu Int.

Lưu ý 1: Khi truyền 1 lambda function vào 1 hàm khác dưới dạng 1 tham số, ta có thể bỏ qua phần định nghĩa kiểu dữ liệu cho các tham số của lambda functionKotlin tự động gán kiểu cho các tham số của lambda function sao cho khớp với lúc khai báo hàm. Ví dụ, ta hoàn toàn có thể gọi hàm printSummary ở trên như sau:

Ta không cần định nghĩa kiểu dữ liệu cho a và b. 2 tham số này sẽ tự động được gán kiểu là Int, bởi Kotlin thông minh, đơn giản là vậy.

Lưu ý 2: Ta cũng không cần phải chỉ ra kiểu dữ liệu trả về cho lambda function, bởi Kotlin cũng sẽ tự động định nghĩa kiểu dữ liệu trả về cho chúng ta (như ở trường hợp trên, kiểu dữ liệu trả về là Int).

Lưu ý 3: Đối với lambda function, khi muốn trả về 1 giá trị, ta không thể dùng từ khóa return. Ta đơn giản chỉ cần nêu ra giá trị đó. Như ví dụ trên là a+b. Tất nhiên giá trị ta nêu ra phải có cùng kiểu dữ liệu trả về của lambda function (như ví dụ trên là Int), nếu không sẽ nhận được thông báo lỗi Type mismatch.

Lưu ý 4: Vì không thể sử dụng từ khóa return, nên ta có thể nêu ra bao nhiêu giá trị tùy thích. Nhưng lambda function sẽ lấy giá trị cuối cùng làm giá trị trả về của hàm:

Lưu ý 5: Nếu lambda function là tham số cuối cùng của 1 hàm, ta có thể viết nội dung của lambda function bên ngoài cặp (). Ví dụ:

Nhìn như này trông code sẽ đẹp hơn và dễ hiểu hơn :).

Tham số it

Khi truyền lambda function vào 1 hàm khác dưới dạng 1 tham số, nếu lambda function đó chỉ có duy nhất 1 tham số, ta có thể bỏ qua việc khai báo tham số đó và sử dụng luôn tham số it. Đây là tham số được tự động generate để sử dụng đối với các lambda function chỉ nhận 1 tham số đầu vào. Ví dụ:

Ta định nghĩa hàm printDouble nhận 1 tham số Int và 1 tham số là lambda function. Lambda function này có duy nhất 1 tham số đầu vào kiểu Int. Bình thường, ta sẽ gọi hàm printDouble như sau:

Tuy nhiên, ta có thể bỏ qua phần khai báo tham số x và sử dụng luôn tham số it (được tự động sinh ra, tương ứng với x):

Ví dụ khác:

Đoạn code trên in ra các phần tử bắt đầu bởi ký tự R.

Do hàm filter nhận 1 lambda function có đặc điểm: có duy nhất 1 tham số đầu vào kiểu String. Nên ta có thể sử dụng luôn tham số it (được tự động generate, có kiểu String). Thay vì phải viết thế này:

Ta không cần tự định nghĩa tham số playerName.

Return trong lambda function

Hãy cùng xem 1 ví dụ. Ở ví dụ sau đây, ta sẽ truyền 1 lambda function vào hàm forEach của 1 intArray. Lambda function này duyệt qua tất cả các phần tử nếu gặp phần tử nào chia hết cho 3, thì hàm lambda này sẽ dừng lại.

Và gọi thử:

Tại sao lại không có gì xảy ra? Bởi câu lệnh return không chỉ kết thúc lambda function, nó còn kết thúc luôn hàm chứa nó là testReturnFunction. Nên câu lệnh println(“End of testReturnFunction()”) không bao giờ được gọi. Để cho Kotlin hiểu rằng, chỉ kết thúc lambda function, ta sửa lại như sau:

Lúc này, khi gọi hàm testReturnFunction() ta sẽ nhận được dòng log như mong đợi:

Trên đây mình đã trình bày những vấn đề cơ bản mà chúng ta cần phải biết về lambda function. Đây cũng sẽ là loại function mà mình rất hay sử dụng trong phát triển ứng dụng Android. Vì nó rất đơn giản, ngắn gọn, tường minh. Nó thay thế được cho các interface cồng kềnh trong Java (mình sẽ nói trong các bài tiếp theo). Hy vọng các bạn nắm rõ được nó. Sau này ở loạt bài Android, mình sẽ có dịp quay trở lại chủ đề này. Còn giờ thì next qua phần khác thôi 😀

Extension function

Chúng ta đều biết rằng, kiểu dữ liệu String cung cấp hàm cho ta biến 1 chuỗi chữ thường thành chữ hoa:

Nhưng giờ nếu ta muốn String có thêm hàm chỉ biến ký tự đầu tiên thành chữ hoa thôi, còn các ký tự khác biến thành chữ thường, thì phải làm thế nào? Có thể bạn sẽ nghĩ đến việc kế thừa lớp String và viết hàm bổ sung theo ý muốn. Nhưng String là lớp final, tức là nó không cho phép kế thừa. Giờ mình muốn mỗi biến String đều được cung cấp hàm như thế này:

Hàm upperFirstLetter hiển thị trong danh sách các hàm gợi ý của String

Kotlin lại cung cấp cho ta 1 tính năng tuyệt vời, đó là extension function. Đây là tính năng cho phép ta mở rộng 1 lớp (trong trường hợp này là String) với các hàm bổ sung mà không cần phải kế thừa lớp đó.

Định nghĩa extension function

Giờ mình sẽ tạo file StringUtils.kt chứa 1 package mới com.duongvu.stringutils, và định nghĩa 1 hàm như sau:

Đây chính là 1 extension function (hàm mở rộng) của kiểu dữ liệu String. Như các bạn thấy, để định nghĩa hàm mở rộng, ta cần phải chỉ ra kiểu dữ liệu (String) trước tên hàm mở rộng (upperFirstLetter). Từ khóa this được sử dụng trong thân hàm biểu diễn cho đối tượng gọi đến hàm upperFirstLetter.

Sử dụng extension function

Sau khi đã định nghĩa xong, để sử dụng extension function thì việc đầu tiên ta phải import package chứa nó. Sau đó ta gọi tới extension function như những hàm bình thường khác của kiểu dữ liệu ta vừa mở rộng (trường hợp này là String):

Các bạn có thể thấy rằng, hàm upperFirstLetter đã xuất hiện trong danh sách gợi ý của IDE (như hình mình gửi bên trên).

Tổng kết

Như vậy ở bài viết này mình đã trình bày về:

  • Top level function
  • Lambda function
  • Extension function

Hy vọng qua 2 bài viết về hàm trong Kotlin vừa qua các bạn đã có được kiến thức nền tảng và kỹ năng cơ bản để sử dụng chúng. Tất nhiên vẫn còn 1 số kiến thức mà mình chưa trình bày ra ở đây. Bởi mình thấy sẽ phù hợp, dễ hiểu và bổ ích hơn khi đan xen chúng vào các kiến thức khác, hơn là việc chỉ nêu ra khái niệm. Các kiến thức đó chắc chắn mình sẽ nêu ra trong các bài viết sắp tới. Và bài tiếp theo mình sẽ bắt đầu trình bày về những khái niệm cơ bản nhất của lập trình hướng đối tượng. Đó là class và object. Đây là những khái niệm cực kỳ quan trọng, và sẽ đi theo ta trong mọi dự án. Hãy theo dõi nhé.



Kotlin Bài 7: Lớp, thuộc tính và phương thức

Lớp (class) là khái niệm cơ bản nhất trong lập trình hướng đối tượng. Nó đặc trưng cho 1 loại đối tượng xác định, với những thuộc tính và hành động xác định. Lớp bao gồm các thuộc tính và các phương thức. Hôm nay, hãy cùng mình tìm hiểu về khái niệm cơ bản nhưng cũng là quan trọng nhất của lập trình hướng đối tượng này nhé.

Định nghĩa lớp

Ta định nghĩa lớp qua từ khóa class, điều này khá giống với Java:

Dòng code trên vừa khai báo 1 lớp rỗng, lớp đơn giản nhất trong lập trình hướng đối tượng. Nó không có thuộc tính cũng chẳng có thân hàm, tuy nhiên ta vẫn có thể khởi tạo 1 đối tượng của lớp Human trên như sau:

Như vậy là không giống với Java, ta không cần từ khóa new để khởi tạo 1 đối tượng mới.

Thuộc tính và hàm khởi tạo của lớp

Lớp ta vừa mới khởi tạo ở phần trước còn khá đơn giản, giờ ta sẽ thêm vài thuộc tính cho nó, đồng thời sẽ thêm cách khởi tạo mới cho class này:

Lớp Human đã được bổ sung 2 thuộc tính là name và age. Và nó có hàm khởi tạo gồm 2 tham số đầu vào kiểu String và int(var name: String, var age: Intđược gọi là primary constructor (hàm khởi tạo chính).

Vậy là với đúng 1 dòng, ta đã vừa khai báo được các thuộc tính của class Human, vừa định nghĩa được hàm khởi tạo ứng với 2 thuộc tính đó. Rất ngắn gọn và dễ hiểu đúng không. Giờ ta sẽ thử khởi tạo 1 đối tượng Human bằng hàm khởi tạo ta vừa định nghĩa:

Đơn giản là khai báo biến có kiểu dữ liệu là Human và khởi tạo cho nó! Theo đúng định nghĩa lớp viết ra ở trên, mình dùng hàm khởi tạo có các tham số đầu vào là String và Int, tương ứng với tên và tuổi. Giờ mình sẽ lấy ra giá trị các thuộc tính của thằng me mà mình vừa khởi tạo:

Cách truy xuất đến thuộc tính của đối tượng rất đơn giản dễ dàng đúng ko. Ta sử dụng cú pháp:

để truy xuất đến thuộc tính của đối tượng me.

Định nghĩa phương thức cho lớp

Từ đầu đến giờ ta đã định nghĩa class Human, có các thuộc tính của riêng nó, nhưng chưa định nghĩa các hành động cho nó thực hiện. Ta sẽ viết định nghĩa 1 phương thức nằm trong class Human như thế này:

Như vậy việc định nghĩa phương thức trong class đơn giản chỉ là định nghĩa hàm để xử lý các thuộc tính trong class đó. Như ở trên ta đã xây dựng 1 phương thức cho phép Human giới thiệu về bản thân mình. Phương thức này sẽ log ra tên, tuổi của đối tượng gọi phương thức:

Như vậy là phương thức introduce đã hoạt động. Ta thấy, khi ở bên trong thân class, thì truy xuất đến các thuộc tính đơn giản là gọi thẳng tên thuộc tính đó ra: $name$age. Còn ở bên ngoài class ta cần sử dụng cú pháp <tên_đối_tượng>.<tên thuộc tính>. Mình sẽ có 1 bài viết riêng về các quy tắc truy xuất đến thuộc tính và phương thức của 1 lớp để các bạn hiểu rõ hơn. Còn giờ thì ta cứ biết cơ bản là vậy đã, ok 😉

Ok, giờ ta sẽ tạo 1 phương thức khác cho quen tay. Thêm hàm sau vào lớp Human:

Ta sẽ gọi hàm greeting này:

Block init

Trong Kotlin class, ta có thể định nghĩa ra 1 block init, bao gồm các câu lệnh được thực hiện ngay sau khi đối tượng của lớp đó được khởi tạo. Ví dụ nhé, ta thêm khối lệnh sau vào lớp Human:

Block init này thực hiện việc log ra dòng thông báo ngay sau khi mỗi đối tượng của class Human được khởi tạo. Thử khởi tạo 1 đối tượng mới:

Như ta thấy các câu lệnh trong block init đã được gọi ngay sau khi đối tượng peter được tạo ra. Và tất nhiên là trước khi phương thức introduce được thực hiện.

Từ khóa this

Trong 1 lớp , từ khoá this trỏ tới đối tượng hiện tại của lớp đó. Nghe hơi trúc trắc nhỉ, mình sẽ demo ngay để các bạn dễ hình dung. Ta viết 1 phương thức như sau:

Phương thức này tăng giá trị của thuộc tính age lên 1 đơn vị. Ở đây, từ khoá this trỏ đến đối tượng hiện tại, tức là đối tượng đang gọi phương thức increaseAge này, và thay đổi thuộc tính age của đối tượng đó. Ta thử gọi phương thức:

Secondary constructor

Một lớp có thể có nhiều hàm khởi tạo khác nhau. Secondary constructor là các hàm khởi tạo khác với hàm khởi tạo chính (primary constructor). Mình sẽ lấy thêm 1 ví dụ khác cho các bạn hiểu rõ hơn nữa nhé. Ta sẽ tạo 1 lớp Animal. Đầu tiên, khai báo lớp và hàm khởi tạo:

Animal là lớp đặc trưng cho động vật, có các thuộc tính là kind (loài) và name (tên)Giờ mình sẽ thêm 1 thuộc tính nữa:

Thuộc tính age có kiểu Int. Thuộc tính này có thể có giá trị hoặc null. Nếu bạn còn chưa rõ các khái niệm về nullable type, hãy đọc lại bài hướng dẫn về Nullable type – kiểu dữ liệu có thể null của mình nhé.

Thêm 1 phương thức:

Đơn giản là log ra câu chào ứng với các giá trị của kind và name. Nếu age được gán giá trị (khác null) thì log thêm cả giá trị của ageToàn bộ lớp Animal của ta sẽ như thế này:

Thử khởi tạo 1 đối tượng, và gọi phương thức getInfo:

Tạo 1 đối tượng Animal khác:

Lần này, dòng log ko có thông báo age vì age đang có giá trị nullCác bạn có thể thấy rằng mỗi khi khởi tạo 1 đối tượng Animal, ngoài kind và name là các thuộc tính bắt buộc phải có trong hàm khởi tạo. Thì thuộc tính age là không bắt buộc. Nếu ta quên không set giá trị cho age thì đương nhiên nó sẽ null. Giờ ta muốn đưa age vào 1 hàm khởi tạo khác mà vẫn muốn giữ hàm khởi tạo ta định nghĩa lúc đầu, phải làm thế nào. Ta sẽ định nghĩa thêm 1 hàm khởi tạo nữa, tất cả (những) hàm khởi tạo được định nghĩa thêm được gọi là Secondary constructor:

Ở đây có 2 điều cần nói:

  • Secondary constructor phải gọi đến primary constructorthis(kind, name) hoặc gọi đến Secondary constructor nào có gọi primary constructor. Tóm lại bằng mọi giá phải tham chiếu đến primary constructor.
  • Secondary constructor không cho phép định nghĩa các thuộc tính, tức là ta không thể sử dụng các từ khóa varval khi định nghĩa Secondary constructor.

Ta thử khởi tạo 1 phương thức dùng hàm Secondary constructor mới được viết xem sao:

Ta sẽ thêm 1 thuộc tính nữa:

Và thêm 1 hàm khởi tạo nữa:

Hàm khởi tạo này gọi đến hàm khởi tạo thứ 2 ta định nghĩa ở trên, do hàm khởi tạo thứ 2 đã tham chiếu đến primary constructor, nên hàm khởi tạo thứ 3 này hoàn toàn hợp lệ. Ok, hi vọng các bạn đã hiểu được về secondary constructor.

Getter và setter

Khác với java, mỗi thuộc tính trong các Kotlin class đều được khởi tạo 1 hàm getter và 1 hàm setter mặc định. Hàm getter của 1 thuộc tính được gọi đến khi ta truy xuất đến giá trị của thuộc tính đó. Hàm setter thì được gọi khi ta thay đổi giá trị của thuộc tính. Để ví dụ mình sẽ định nghĩa thêm 1 thuộc tính trong lớp Animal trên và viết getter setter cho nó. Các bạn hãy nhớ kỹ rằng, nếu ta ko viết getter setter cho 1 thuộc tính thì thuộc tính đó sẽ có các hàm getter và setter mặc định, các hàm đó đơn giản chỉ lấy và gán giá trị cho thuộc tính của chúng ta.

Giờ thử tạo 1 đối tượng và lấy giá trị của thuộc tính isSafe:

Kết quả:

Như vậy khi lấy giá trị của isSafe thì hàm getter ứng với thuộc tính isSafe đã được gọi. Ta sẽ thử thay đổi giá trị của isSafe xem sao:

Vậy khi thay đổi giá trị của isSafe thì hàm setter đã được gọi.

Lưu ý: Hàm getter và setter thường được viết lại cho các thuộc tính mà giá trị của nó phụ thuộc vào gía trị của các thuộc tính khác. Như ví dụ trên, giá trị của thuộc tính isSafe phụ thuộc vào gía trị của kind. Nếu trong hàm getter hay setter mà các bạn truy xuất đến thuộc tính tương ứng của nó thì sẽ xảy ra lỗi. Mình ví dụ như sau, ta sẽ thay đổi lại nội dung hàm getter ở trên:

Hàm getter của isSafe trả về đúng giá trị của isSafe nghe hợp lý đúng ko. Thử xem:

Kết quả là dòng log đỏ chói : Exception in thread “main” java.lang.StackOverflowError 😀 . Lý do vì bản thân hàm getter ở trên đã return về giá trị của isSafemà mỗi khi truy xuất đến isSafe thì hàm getter lại được gọi, điều đó tạo ra 1 vòng lặp không có điểm dừng, đây chính là nguyên nhân exception java.lang.StackOverflowError bị văng ra. Vì vậy, hãy luôn nhớ, chỉ viết getter và setter cho các thuộc tính mà giá trị của nó phụ thuộc vào các thuộc tính khác. Còn nếu không, setter và getter đã được mặc định viết cho chúng ta rồi, chúng ta ko nên chọc vào chúng nữa.



Kotlin Bài 8: Kế thừa trong Kotlin

Xin chào, hôm nay mình sẽ trình bày về kế thừa lớp trong Kotlin. Đây là khái niệm rất quan trọng trong lập trình hướng đối tượng. Tại sao quan trọng thì mình sẽ trình bày trong xuyên suốt bài viết. Kế thừa là việc 1 lớp con thừa hưởng toàn bộ các thuộc tính và phương thức của lớp cha, đồng thời có thể có thêm những thuộc tính và phương thức mà lớp cha không có.

Định nghĩa kế thừa

Các bạn vẫn giữ code của bài trước chứ, ta đã định nghĩa lớp Animal đặc trưng cho động vật. Bây giờ ta sẽ định nghĩa 1 lớp Cat kế thừa Animal để làm rõ hơn về khái niệm này nhé. Ta định nghĩa như sau:

Ta đã tạo ra 1 lớp Cat kế thừa lớp Animal đã tạo buổi trước bằng cách khai báo Animal sau dấu :. Lớp Cat vẫn có các thuộc tính kindname như Animal. Nhưng đã có thêm 1 thuộc tính mới, là thuộc tính color. Và hàm khởi tạo của Cat sẽ gọi đến hàm khởi tạo Animal(kind, name) của Animal. Vì sao đối tượng Cat lại có kind và name? Đơn giản vì Cat kế thừa từ Animal, nghĩa là mọi đối tượng Cat chính là Animal mở rộng, và đều có các thuộc tính của Animal. OK, còn color thì sao. Đây là thuộc tính mới của Cat mà Animal không có. Vì Cat là Animal mở rộng mà :)) Cat có toàn bộ các thuộc tính và phương thức của Animal, thêm vào đó là các phương thức, đối tượng mới của riêng Cat.

Ủa nhưng mà sao lại có báo lỗi ở phần khai báo kế thừa: This type is final, so it cannot be inherited fromNguyên nhân là mặc định trong Kotlin, các class được khai báo được hiểu là final class, tức là không thể kế thừa. Vì vậy ta sẽ phải thêm từ khóa open trước class Animal để lớp này có thể được kế thừa bởi lớp Cat:

OK, giờ đã hết lỗi. Ta sẽ thử tạo 1 đối tượng Cat và sử dụng các phương thức của Animal:

Như vậy là các đối tượng Cat hoàn toàn sử dụng được phương thức của Animal, do lớp Cat kế thừa lớp Animal. Ta thậm chí có thể khai báo như thế này:

Ở đây ta khai báo 1 đối tượng có kiểu Animal và khởi tạo nó với Cat. Điều này hoàn toàn hợp lệ bởi khi Cat kế thừa Animal, thì Cat chính là Animal (mở rộng). Nhưng nếu ta khai báo thế này:

Thì lập tức IDE sẽ báo lỗi. Bởi lẽ Animal là lớp cha của Cat, Cat chắc chắn là 1 Animal, nhưng 1 Animal có thể là Bird, Dog, Chicken, … chứ chưa chắc đã là Cat. Vì vậy ta khai báo đối tượng kiểu Cat mà khởi tạo kiểu Animal thì sẽ là không hợp lệ.

Override

Ye, như ở phần trên ta đã biết, 1 đối tượng của lớp con (Cat) có thể gọi được phương thức của lớp cha (Animal). Ta còn có thể làm được nhiều hơn thế: ta có thể mở rộng phương thức đã định nghĩa ở lớp cha. Mình sẽ trình bày ngay đây: ở lớp Animal ta đã khai báo phương thức getInfo, và phương thức này hoàn toàn sử dụng được với các đối tượng Cat. Nhưng giờ đây ta muốn getInfo trả về thêm màu lông của đối tượng Cat. Sau đây là cách làm. Ta định nghĩa hàm getInfo ở lớp Cat như sau:

Và thêm từ khóa open vào dòng định nghĩa phương thức getInfo ở lớp Animal:

Ở đây, trong lớp Cat ta đã viết lại phương thức getInfo của lớp Animal, nên ta phải dùng từ khóa override. Tuy nhiên, chỉ có những phương thức open mới cho phép ta định nghĩa lại, thành ra ta phải thêm từ khóa open khi định nghĩa getInfo ở lớp Animal, dễ hiểu đúng không? Lúc này ta chạy lại chương trình. Kết quả console sẽ không in ra dòng log nào. Vì sao? Bởi với từ khóa override, phương thức getInfo ở class Animal đã bị ghi đè hoàn toàn bởi phương thức getInfo ở class Cat. Mà như ta thấy trong class Cat, phương thức getInfo không thực hiện bất cứ điều gì, dẫn đến không có dòng log nào được in ra ở console. Giờ ta sẽ thử in ra các thuộc tính của Cat xem sao:

Kết quả:

Ồ, vậy là ổn rồi. Nhưng như bạn thấy đấy, mình đã phải copy phương thức getInfo ở Animal để cho vào Cat. Thế thì sinh ra hướng đối tượng với cả kế thừa để làm gì, đúng không. Ta có cách khác để làm điều này, đơn giản chỉ cần gọi:

Là phương thức getInfo ở lớp cha (Animal) sẽ được thực thi. Ta muốn in ra màu lông của Cat nữa thì sao, đơn giản là thêm dòng lệnh:

Như vậy phương thức getInfo ở Cat sẽ như sau:

Chạy lại chương trình ta thu được:

Lưu ý: Bạn có thể định nghĩa thêm bất kỳ các phương thức mới nào ở class Cat, nhưng chúng sẽ chỉ được gọi bởi các đối tượng Cat, chứ ko thể gọi bởi các đối tượng Animal.

Sử dụng interface

Interface trong Kotlin cũng khá giống với bên Java. Nó chứa các phương thức mà các lớp kế thừa nó sẽ thực hiện. Chúng ta cũng có thể định nghĩa các thuộc tính trong interface. Thôi ta sẽ đi vào ví dụ cho dễ hiểu nhé. Ta định nghĩa interface IAction như sau:

Như các bạn thấy, interface trên có 2 phương thức makeSound và eat. Ta sẽ cho lớp Cat kế thừa interface này:

Lưu ý: khi muốn kế thừa thêm bất kỳ class hay interface nào, ta chỉ cần điền thêm tên class hay interface đó, ngăn cách nhau bới dấu 

Giờ IDE sẽ thông báo lỗi cho ta biết rằng, ta cần định nghĩa các phương thức có ở trong interface IAction vào lớp Cat. Okie, định nghĩa thôi, và ta viết luôn nội dung cho các phương thức đó (nên nhớ ở trong IAction ta mới định nghĩa tên phương thức mà chưa viết nội dung):

Sau đó thử gọi :

Kết quả:

Vậy là đối tượng Cat đã có thể sử dụng các phương thức khai báo trong IAction nhờ kế thừa interface này. Ta cũng có thể viết nội dung phương thức ngay trong interface, đây là 1 cải tiến lớn của Kotlin so với Java. Ta sử lại nội dung phương thức eat trong IAction:

và sửa lại trong class Cat như sau:

Lại là từ khóa super, từ khóa này dùng để gọi tới phương thức tương ứng của cha (trường hợp này “cha” là IAction).

Thuộc tính trong interface

Cũng giống như phương thức, ta có thể định nghĩa các thuộc tính trong interface và sử dụng chúng ở trong class kế thừa interface đó. Ví dụ ta thêm thuộc tính numberOfFoot vào interface IAction:

Lúc này, khi khai báo numberOfFoot trong class, ta đã có thể (và bắt buộc )khởi tạo giá trị cho nó. Hoặc ta có 1 cách khác:

Vậy là ta đã set được giá trị cho numberOfFootTa định nghĩa thêm 1 phương thức move trong IAction thế này:

Lưu ý: Các phương thức được viết nội dung trong interface thì không bắt buộc phải khai báo trong class kế thừa interface đó.

Giờ ta thử sử dụng phương thức move mới khởi tạo:

Vậy là giá trị của thuộc tính numberOfFoot đúng là 4 như ta đã khai báo trong class Cat.

Abstract class (lớp trừu tượng)

Về cơ bản abstract class cũng tương tự như interface, nó cho phép ta định nghĩa các thuộc tính, phương thức để sử dụng trong class kế thừa nó. Tuy nhiên abstract class có 1 nhược điểm so với interface là: 1 class chỉ được phép kế thừa từ 1 class khác, trong khi đó có thể kế thừa tùy ý số interface. Vì vậy khi class của bạn đã kế thừa 1 class rồi thì sẽ không thể sử dụng abstract class được nữa. Dù sao mình cũng sẽ demo 1 ví dụ về abstract class:

Ta tạo ra 1 abstract class AbstractAction. Trong này ta định nghĩa 2 phương thức makeSoundAbstract và eat. Phương thức makeSoundAbstract ta chưa viết luôn thân hàm nên phải thêm từ khóa abstract ở đầu, còn đối với eat thì không cần. Ok, giờ ta sẽ cho class Cat của ta kế thừa class AbstractAction này:

Ngay lập tức IDE sẽ báo cho ta 1 lỗi: Only one class may appear in supertype list

Tức là chỉ 1 class được xuất hiện ở danh sách kế thừa. Class Cat của ta đã kế thừa Animal, nên không thể kế thừa thêm 1 class khác (AbstractClass). Ta thử xóa bỏ phần kế thừa Animal và các phương thức liên quan đến Animal đi xem sao. Sau đó override lại phương thức makeSoundAbstract (Phương thức eatFood không bắt buộc override vì nó đã được viết thân hàm).

Rồi, thử các phương thức mới xem nào:

Trùng lặp tên phương thức khi kế thừa nhiều interface

Đôi khi trong các class hay interface mà ta kế thừa, có những phương thức trùng tên với nhau. Vậy, chỉ với từ khóa super, làm sao để gọi chính xác phương thức ứng với interface (class) mà ta mong muốn. Ví dụ mình sửa phương thức eatFood trong AbstractAction thành như sau:

và sửa phương thức eat trong IAction thành như sau:

Vậy là 2 phương thức đã giống nhau từ tên cho đến tham số đầu vào, giờ hãy để ý trong phương thức override fun eat(food: String) của class Cat: Many supertypes available, please specify the one you mean in angle bracket, egg ‘super<Foo>’Nghĩa là: phương thức này đã được định nghĩa ở nhiều lớp hoặc interface cha (IAction và AbstractAction). Ta cần chỉ rõ muốn sử dụng phương thức ở lớp cha nào. Cách làm như sau:

Thử sử dụng phương thức eat này xem:

Kết quả:

Vậy là ta đã định rõ được phương thức ta muốn sử dụng là tới từ lớp (interface) cha nào.

Tổng kết

Vậy là bài này mình đã trình bày xong những khái niệm cơ bản về kế thừa trong lập trình hướng đối tượng với Kotlin. Các kiến thức về interface, abstract class, override mình đã đưa ra kèm các ví dụ cụ thể. Hy vọng các bạn có thể nắm rõ nội dung bài học.




Xây dựng ứng dụng chụp ảnh và filter camera trên Android (p1)

Ngày nay, tính năng chụp và chỉnh sửa ảnh đã trở nên cực kỳ phổ biến trong các ứng dụng smart phone. Hôm qua nhân dịp vọc vạch nấu nướng mình có download và sử dụng app Feedy để thực hành theo. Chợt nhận ra Feedy cũng có 1 module cho phép user chụp ảnh (có filter camera) và chỉnh sửa ảnh. Mình thấy rất thú vị nên mình cũng clone luôn để làm 1 loạt bài tutorial về filter trên Android. Sau đây là demo ứng dụng của mình:

Các chức năng chính trong app mình xây dựng sẽ bao gồm:

  • Chụp ảnh (có filter camera, sử dụng camera trước hoặc camera sau)
  • Chỉnh sửa ảnh (lựa chọn filter)
  • Cắt ảnh, viết caption cho ảnh và chia sẻ
  • Đơn giản thôi đúng không nào. Giờ mình sẽ bắt tay vào làm nhé.

Các thư viện sử dụng trong dự án

Lưu ý: Link source code ở cuối bài viết 😉

Mình sẽ dùng các thư viện sau:

  • Android – GPU Image Plus: Đây là 1 thư viện filter khá mạnh với nhiều tính năng. Nó cung cấp cho ta các tính năng cơ bản như điều khiển độ sáng, độ phân giản, độ tương phản, … Ngoài ra thư viện này cung cấp cho ta tính năng sử dụng những file Lookup table (LUT) để áp dụng filter lên bitmap. Ngoài ra còn rất nhiều tính năng về xử lý ảnh khác nữa. Phần core của thư viện này viết bằng C/C++.
  • Glide: Thư viện load ảnh.
  • ButterKnife: Thư viện inject view, đỡ phải sử dụng findViewById rất dài dòng và nhàm chán.
  • SimpleCropView: Thư viện cho phép ta cắt 1 vùng của bitmap.
  • RoundedImageView: Thư viện bo góc cho imageview, do mình lười custom nên mình cũng dùng thư viện luôn cho nhanh.

Đó là những thư viện cơ bản mình dùng trong project.

Sơ lược về luồng đi của ứng dụng

Ứng dụng của chúng ta gồm 4 màn hình:

  • Splash screen hiển thị logo của ứng dụng đồng thời xin quyền đọc ghi trên bộ nhớ máy (để lưu ảnh), quyền sử dụng camera .
  • Camera screen: Chụp ảnh, lựa chọn hiệu ứng
  • Result screen: Sau khi chụp ảnh, hiển thị ảnh đầu ra, cắt ảnh, chỉnh sửa ảnh
  • Share screen: Hiển thị ảnh cuối cùng sau khi chỉnh sửa, viết caption và share ảnh.

Bắt đầu

Việc đầu tiên sẽ là… tạo project rồi. Tất nhiên, các bạn hãy tạo 1 project để code nhé 😀

Import thư viện

Mình sẽ import các thư viện kể trên vào dự án, thêm các lệnh này vào file build.gradle (level app):

Tạo base cho dự án

Permission

Ứng dụng cần quyền sử dụng camera và quyền đọc ghi file trên thiết bị Vì vậy ta cần thêm các dòng sau vào file AndroidManifest.xml:

Font chữ

Mình sử dụng font Roboto cho dự án này. Trong thư mục main, mình sẽ tạo 1 thư mục assets chứa thư mục con Fonts (là nơi mình đặt các file Font chữ)

Gói image filter

Trong dự án này mình sẽ sử dụng các file Lookup table (LUT) – là các file chứa các thông số filter để chỉnh sửa màu sắc cho ảnh. Các file này chứa trong thư mục assets mình đã tạo bên trên:

Đây là các file chứa các thông số cho các filter mình sử dụng trong app.

BaseActivity

Như ở phần trên mình đã phân tích, luồng đi của ứng dụng qua 4 màn hình. Mình sẽ sử dụng các Activity tương ứng với mỗi màn, vậy ta có tổng cộng 4 Activity. Để code được ngắn gọn và tường minh, mình sẽ viết 1 lớp BaseActivity.java. Lớp này chứa 1 số phương thức chung có thể sử dụng cho tất cả các Activity trong dự án. Mọi Activity sẽ kế thừa từ lớp BaseActivity này:

Utils

Trong ứng dụng, có những chức năng ta cần đến kích thước màn hình, hoặc cần mở App Setting để user cấp quyền cho app. Những chức năng này mình viết trong 1 lớp DeviceUtils.java. Mình sẽ tạo 1 package đặt tên là utils để chưa file DeviceUtils.java.

Màn hình splash

Khai báo layout

Sau khi tạo xong base dự án ta sẽ bắt  tay vào màn hình đầu tiên, là màn hình splash. Đây là màn hình có chức năng hiển thị ra 1 dòng text giới thiệu dưới dạng animation, đồng thời xin cấp các quyền mà ứng dụng sẽ sử dụng. Đây là file layout activity_spalsh.xml:

Đơn giản chỉ là 2 TextView giới thiệu về app thôi.

Khai báo SplashActivity trong AndroidManifest

Giờ ta sẽ tạo 1 file đặt tên là SplashActivity để viết nội dung cho màn hình splash. Class này sẽ kế thừa BaseActivity. Đồng thời trong AndroidManifest, ta khai báo SplashActivity là màn hình đầu tiên của ứng dụng (thay vì MainActivity mặc định):

Lớp SplashActivity:

Đơn giản là khai báo layoutId và khai báo 1 TextView thôi đúng không. Giờ ta muốn cho TextView này 1 animation. Khi kết thúc animation, ta sẽ kiểm tra các quyền đọc ghi file, camera được cấp chưa. Nếu chưa được cấp thì sẽ bật ra dialog xin cấp quyền. Ta sẽ viết hàm animate để làm điều này.

Animation

Như các bạn thấy mình sử dụng AnimationListener để lắng nghe sự kiện animation kết thúc. Giờ ta sẽ viết hàm askForPermission để thực hiện bật dialog xin quyền từ user trong trường hợp các quyền ta sử dụng chưa được cấp phép. Nếu được cấp rồi thì nhảy thẳng đến màn hình Camera.

Phương thức xin cấp permission

Quay lại với BaseActivity 1 chút. Trong app này, mình chỉ có 1 chỗ cần phải xin quyền từ user, đó là ở SplashScreen. Tuy nhiên trong thực tế, 1 app có thể có nhiều lớp cần implement tính năng xin cấp quyền. Vì vậy mình sẽ viết hàm askCompactPermissions thực hiện tính năng này trong lớp BaseActivity. Các lớp kế thừa lớp BaseActivity sẽ đều có thể sử dụng hàm askCompactPermissions này để xin cấp quyền.

Đầu tiên ta viết 1 lớp PermissionUtils trong package utils:

Lớp này chứa các quyền ta cần xin (đọc ghi file, camera) và 1 interface PermissionResult xử lý các hành động của người dùng.

Trong BaseActivity, mình khởi tạo các biến sử dụng cho việc xin cấp quyền:

Và viết hàm askCompactPermissions như sau:

Các hàm trên đảm nhiệm chức năng kiểm tra và hiển thị hộp thoại cấp quyền trong trường hợp người dùng chưa cấp quyền cho ứng dụng. Do đây ko phải trọng tâm ứng dụng nên mình sẽ ko nói quá kỹ phần này. Các bạn có thể tìm hiểu thêm hoặc sử dụng các thư viện hỗ trợ.

Sử dụng phương thức cấp permission

OK, đã viết hàm xin permission xong ở BaseActivity, giờ quay trở lại SplashScreen. Ta sẽ gọi hàm xin permission trong askForPermission:

Nội dung hàm này là: trong trường hợp permission được cấp, ta nhảy đến MainActivity (sẽ định nghĩa sau). Nếu không được cấp, thoát app. Nếu người dùng chọn option vĩnh viễn không cấp, ta sẽ hiện dialog yêu cầu người dùng bật App Setting và cấp lại quyền, nếu không sẽ thoát app.

Ngon rồi, giờ ta sẽ gọi hàm animate trong onResume của SplashActivity để thực hiện animation cho TextView:

Như vậy là mỗi khi vào app, hàm animate sẽ được gọi. Nó tạo ra 1 animation cho textview và khi animation kết thúc, kiểm tra trạng thái cấp quyền của ứng dụng.

Tổng kết

Ở phần đầu tiên của loạt bài hướng dẫn xây dựng app filter ảnh, mình đã giới thiệu tới các bạn các thư viện sử dụng trong app, cách tạo base project và xây dựng màn hình splash cho ứng dụng. Bài sau mình sẽ hướng dẫn xây dựng module chọn ảnh với các chức năng filter camera, và chức năng chọn ảnh trực tiếp từ bộ nhớ. Hãy theo dõi nhé.




Xây dựng ứng dụng chụp ảnh và filter camera trên Android (p2)

Hello, mình là Dương Vũ. Đây là bài viết thứ 2 trong loạt bài hướng dẫn xây dựng ứng dụng chụp ảnh và filter camera trên Android. Ở bài viết trước mình đã hướng dẫn xây dựng màn hình Splash có chức năng hiện ra dòng giới thiệu về ứng dụng, đồng thời xin cấp các permission sử dụng trong ứng dụng. Bài này, ta sẽ cùng nhau xây dựng module chính của ứng dụng: module camera và chụp ảnh. Sau khi hoàn thành bài viết này, ứng dụng của ta sẽ có thêm các chức năng:

  • Camera kèm theo hiệu ứng (filter)
  • Hiển thị danh sách filter để lựa chọn khi chụp ảnh
  • Lưu trữ ảnh trên bộ nhớ thiết bị
  • Các chức năng cơ bản của camera: quay trước, quay sau, on/off chế độ chụp flash.

Nào, bắt đầu nhé!

Màn hình Main

Ok, mình sẽ sử dụng luôn lớp MainActivity mà IDE tạo ra cho  lúc khởi tạo dự án để viết chức năng camera. Màn hình này bao gồm 1 camera view có kích thước chiều rộng full màn hình, chiều cao bằng chiều rộng (tỉ lệ 1:1). Có 1 button để chụp ảnh, 1 recyclerView để hiển thị danh sách hiệu ứng, các button chức năng của camera (on/off flash, đổi camera). Ngoài ra màn hình còn có thêm 1 button cho phép chọn ảnh từ gallery. Ok, ta hãy bắt tay vào xây dựng layout cho nó.

Xây dựng layout

Ta chỉnh sửa lại file activity_main.xml như sau:

Bạn đừng lo lắng khi nhìn sang preview thấy layout của ta như 1 mớ shit. Ta sẽ chỉnh sửa lại kích thước cho chúng ở phần code Java. Ở đây, ta chỉ cần lưu ý tới 1 view đặc biệt, đó là org.wysaid.view.CameraRecordGLSurfaceView. Đây chính là view thuôc thư viện xử lý ảnh mà mình đã trình bày ở bài đầu tiên. Nó sẽ có nhiệm vụ hiển thị camera preview cho chúng ta.

MainActivity.java

Khai báo layout đã xong, giờ ta sẽ chuyển tới phần Java code. Việc đầu tiên là ta sẽ bắt MainActivity kế thừa BaseActivity ta đã tạo ra (chứ không phải kế thừa AppCompatActivity như mặc định), sau đó sẽ là bindView cho nó:

Setup camera

Tiếp theo, mình sẽ viết 1 phương thức setUpCameraView, phương thức này sẽ chỉnh sửa lại kích thước của cameraView, do ở phần layout xml, kích thước của cameraView chưa đúng tỉ lệ 1:1. Ngoài ra, ta cũng sẽ bắt các sự kiện cho các button on/off flash và config 1 số thuộc tính của cameraview:

Ok, việc setup cho cameraview đến đây gần như đã xong. Tuy nhiên còn 1 bước nữa. Như đã giới thiệu ở bài trước, mình có sử dụng những file LUT chứa trong thư mục assets để tạo ra hiệu ứng cho camera. Vì vậy, ta cần khai báo (load) các file LUT này cho đối tượng cameraView của chúng ta. Trước hết, hãy tạo 1 callback theo dõi việc load các file LUT success hay failed:

Sau khi tạo callback xong, ta thêm câu lệnh sau vào phương thức setUpCameraView để load các file LUT:

Ok, vậy là các bước config cho cameraView đã hoàn thành. Ta sẽ gọi hàm setUpCameraView vừa viết ở trong hàm createView:

và không quên tắt camera khi rời khỏi activity hiện tại:

 

Xây dựng list filter

Bây giờ việc tiếp theo của ta là hiển thị list các filter để cho người dùng chọn trong lúc chụp ảnh. Ta sẽ dùng recyclerView để hiển thị các filter này. Việc đầu tiên chúng ta cần làm để xây dựng list filter là định nghĩa 1 đối tượng chứa các thông tin về filter đó.

Đối tượng FilterData.

Mình sẽ tạo 1 package model. Bên trong chứa lớp FilterData có nội dung như sau:

Như các bạn thấy lớp này chỉ là 1 lớp Java thuần túy, không có gì đặc biệt cả. Nó chứa 3 trường:

  • name: Tên hiển thị trên app của filter.
  • rule: config của filter để thư viện xử lý ảnh thực hiện theo. Thực chất đây là tên các file LUT. Nếu các bạn tìm hiểu sâu thêm về thư viện xử lý ảnh này, các bạn sẽ thấy rằng có thể viết các config để thực hiện bất kỳ hiệu ứng nào ta mong muốn (chỉ cần có giá trị tham số). Tuy nhiên mình sẽ viết ở 1 bài khác. Ở bài này ta chỉ cần quan tâm đến tên config chứa file LUT mà thôi.
  • imageId: Id ảnh preview của filter. Mình sẽ đặt sẵn các ảnh preview ứng với từng filter trong thư mục drawable. Và trường imageId này sẽ có giá trị là id của các ảnh đó để hiện thị lên trên app.

Như vậy là ta đã xây dựng xong đối tượng chứa các thông tin về 1 filter. Giờ ta sẽ xây dựng adapter hiển thị list filter.

Xây dựng List Filter Adapter

Đầu tiên ta sẽ tạo layout cho các item của adapter, đặt tên là item_list_filter.xml:

Hãy để ý tới dòng:

Khi 1 filter được chọn, nó sẽ có 1 background riêng, để dễ phân biệt với các filter còn lại. Ta sẽ viết 2 file xml để thể hiện 2 trạng thái (được chọn và không được chọn) của filter. Trong thư mục drawable, tạo file bg_item_filter_selected.xml tương ứng với trạng thái filter đang được chọn:

Tạo thêm 1 file bg_item_filter_unselected.xml

Như các bạn thấy, ở trạng thái không được chọn, ta sẽ set cho filter đó có 1 background trống trơn.

Ok, mọi thứ liên quan đến layout đã xong, giờ ta sẽ viết adapter cho list filter. Ta tạo package adapter và định nghĩa lớp ListFilterAdapter như sau:

Adapter này cực kỳ đơn giản, nó chỉ chứa 1 interface xử lý sự kiện chọn filter, các bạn hãy tìm hiểu nhé. Ok, như vậy ta đã xây dựng xong adapter, giờ sẽ hiển thị nó lên app.

Hiển thị list filter

Quay trở lại MainActivity, mình sẽ tạo ra 1 mảng các String, mảng này chứa tên các file LUT mà mình sẽ truyền vào các đối tượng FilterData:

Và bây giờ sẽ tạo 1 phương thức setUpListFilterEffect để hiển thị list các filter lên recyclerview:

Như các bạn thấy, phương thức trên khởi tạo ra 1 list các đối tượng FilterData và truyền list đó vào adapter để hiển thị lên recyclerView. Đồng thời ta cũng implement các câu lệnh khi có 1 filter được chọn:

Ok, hy vọng code mình viết đủ rõ ràng để các bạn có thể hiểu. Giờ ta sẽ gọi phương thức setUpListFilterEffect này trong hàm createView:

Chức năng chụp ảnh

Ta sử dụng ButterKnife để bắt sự kiện click vào button chụp ảnh:

Như bạn thấy, khi click vào nút chụp ảnh, ta sẽ lưu lại 1 file jpg trong thư mục FilterImageDemo và nhảy sang màn hình chỉnh sửa ảnh (CameraResultActivity). Ta cũng truyền dữ liệu về filter đang được chọn và đường dẫn ảnh đã lưu sang màn hình chỉnh sửa. Tuy nhiên giờ ta chưa cần quan tâm đến màn hình chỉnh sửa ảnh này. Đơn giản hãy tạo 1 activity có tên CameraResultActivity và khai báo nó trong file AndroidManifest:

Chức năng tùy chọn camera trước (sau)

Ta sử dụng ButterKnife để bắt sự kiện click cho button đảo camera:

Chức năng đóng màn hình camera

Ta sử dụng ButterKnife để bắt sự kiện click cho button thoát khỏi màn hình camera:

Chức năng chọn ảnh trong gallery

Ta viết thêm 1 chức năng cho phép người dùng chọn ảnh trong gallery để chỉnh sửa thay vì chụp ảnh trực tiếp.

Như các bạn thấy khi ấn vào button pick image, ta đơn giản hiển thị màn hình CameraResultActivity. Khác với khi chụp ảnh, ta không truyền thêm dữ liệu nào khi chuyển qua màn hình CameraResultActivity này. Như vậy ở trong lớp CameraResultActivity, ta sẽ cần xử lý 2 trường hợp:

  • Không có data truyền sang (chọn ảnh trong gallery)
  • Có data truyền sang, hiển thị ảnh với các dữ liệu truyền từ màn hình chụp ảnh.

Tổng kết

Như vậy ở bài này mình đã hướng dẫn xong tính năng chụp ảnh. Ở bài tiếp theo (và cũng là bài cuối cùng) mình sẽ hướng dẫn nốt về chức năng chỉnh sửa cũng như chia sẻ ảnh được chọn. Hãy theo dõi nhé.




Xây dựng ứng dụng chụp ảnh và filter camera trên Android (p3)

Hello, hôm nay mình sẽ trình bày nốt về các tính năng cuối cùng trong loạt bài về ứng dụng filter ảnh này. Các tính năng sẽ xây dựng sau khi hoàn thành bài viết:

  • Load ảnh đã chụp từ camera
  • Load ảnh từ bộ nhớ
  • Chỉnh sửa ảnh
  • Cắt ảnh
  • Chia sẻ ảnh

CameraResultActivity

Yeah, ở bài trước, mình đã tạo 1 CameraActivity trống để xử lý các dữ liệu truyền sang từ màn hình chụp ảnh (MainActivity). Sẽ có 2 trường hợp:

  • Xử lý ảnh chụp từ camera

  • Chọn ảnh từ thư viện

Với trường hợp này, ta đơn giản chỉ hiển thị CameraResultActivity và truyền cho nó 1 bundle rỗng. Ok, và nhiệm vụ của ta là sẽ phải xử lý các logic tương ứng với dữ liệu từ MainActivity truyền sang.

Xây dựng layout

Ta sẽ xây dựng layout cho màn hình chỉnh sửa ảnh. Mình tạo 1 file activity_camera_result.xml như sau:

Giao diện này cũng tương tự như giao diện màn hình chụp ảnh (MainActivity), khác mỗi cái là ta thay thế CameraView bằng 1 đối tượng CropImageView. Đây là 1 đối tượng ImageView đặc biệt, cho phép chúng ta lựa chọn vùng ảnh và cắt (crop). Màn hình này cũng chứa 1 list các hiệu ứng cho ta filter lên ImageView. Ok, đơn giản vậy thôi. Hãy tập trung vào việc xây dựng logic!

Xử lý ảnh chụp từ camera

Trước tiên, mình sẽ khai báo 1 biến String để lưu đường dẫn ảnh được truyền sang từ MainActivity (nếu có). 2 biến Bitmap để lưu bitmap của ảnh trước và sau khi filter. Sau đó mình viết 1 hàm get dữ liệu từ MainActivity truyền sang:

Ta đồng thời cũng bind các view từ layout sang activity (sử dụng Butter Knife):

Tiếp theo sẽ gọi hàm getData vừa viết trong hàm onCreate, đồng thời ta sẽ chỉnh sửa lại kích thước các view 1 chút cho đẹp mắt:

Cuối cùng là viết logic xử lý ảnh chụp từ Camera (thực chất là hiển thị file ảnh có đường dẫn lưu trong biến imagePath:

Vậy là khi imagePath có giá trị, ta sẽ hiển thị ảnh lên ivCrop, đồng thời tạo sẵn 1 vùng crop mặc định (là toàn bộ ảnh). Logic này khá đơn giản đúng không.

Xử lý logic chọn ảnh từ thư viện

Trường hợp này được nhận biết bởi việc imagePath không có giá trị (MainActivity truyền 1 bundle rỗng sang). Nên ta sẽ xử lý như sau:

Sau đó, viết lại hàm onActivityResult như thế này:

Trong trường hợp người dùng có chọn ảnh từ thư viện, ta load ảnh đó lên ivvCrop (xử lý tương tự như trường hợp load ảnh chụp từ Camera). Nếu người dùng không chọn ảnh, ta finish activity này liền. Logic cũng khá đơn giản phải không. Ở đây, mình có viết thêm 1 hàm getRealPathFromURI để lấy đường dẫn ảnh mà người dùng chọn:

Vậy là mình đã trình bày xong logic hiển thị ảnh. Giờ sẽ chuyển qua phần xử lý filter cho ảnh.

Chỉnh sửa ảnh

Chức năng này hoàn toàn tương tự như chức năng filter camera mình đã nói ở bài trước. Ta cũng sẽ tạo 1 hàm để hiển thị danh sách các filter lên như sau:

Ta tiếp theo ta sẽ  viết 1 asynctask thực hiện công việc apply filter lên ivCrop:

Cuối cùng, trong hàm setupListFilterEffect, ta thêm đoạn code set sự kiện listener cho item của recycler view:

Vậy là đã hoàn tất việc filter cho ảnh.

Crop ảnh

Giờ ta sẽ làm đến chức năng crop ảnh. Bản thân CropImageView đã cho phép ta chọn vùng ảnh để crop, công việc của ta bây giờ chỉ là xử lý vùng ảnh được chọn đó thôi. Mình sẽ viết hàm xử lý sự kiện xác nhận crop ảnh (khi ấn vào nút continue hoặc done):

Đơn giản là sử dụng hàm startCrop của CropImageView để cắt ảnh, sau đó lưu vào thư mục FeedyPhoto/Filtered. Cuối cùng là truyền đường dẫn ảnh đã lưu sang FinalImageActivity. Đây sẽ là activity có chức năng hiển thị ảnh cuối cùng sau khi chỉnh sửa (filter, crop) và chia sẻ ảnh.

FinalImageActivity

Trước hết mình sẽ tạo 1 activity trống như thế này:

Sau đó khai báo nó trong AndroidManifest.xml

Xây dựng layout

Mình tạo 1 file layout đặt tên là activity_final_image.xml như sau:

Activity này cơ bản là có 1 EditText để viết caption, 1 ImageView để hiển thị ảnh (sau khi chỉnh sửa), và 1 button để chia sẻ ảnh.

Hiển thị ảnh sau khi chỉnh sửa

Đầu tiên mình sẽ khai báo các view sử dụng trong activity trước:

Đơn giản là 1 ImageView và 1 EditText thôi. Thêm vào đó là 1 biến String để chứa đường dẫn ảnh truyền sang từ màn hình CameraResultActivity.

Mình viết 1 hàm getData để lấy dữ liệu truyền sang từ CameraResultActivity:

Hàm này sẽ get được đường dẫn ảnh cần hiển thị. Sau khi lấy được đường dẫn rồi thì hiển thị ra thôi:

Vậy là xong, cùng đi sang chức năng chia sẻ nào.

Chia sẻ ảnh

Mình sẽ bắt sự kiện click cho button có id bt_post:

Tổng kết

Vậy là mình đã trình bày xong toàn bộ các chức năng của app filter ảnh và camera. Toàn bộ source code các bạn lấy tại https://github.com/nanashi1111/ImageFilterDemo. Hãy đọc code và chỉnh sửa cũng như thêm mới các chức năng để hiểu rõ hơn nữa nhé. Nếu có vấn đề gì chưa rõ hãy comment, mình sẽ giải thích.