Category: Uncategorized

  • Lottie Animations in an Android App

    Lottie Animations in an Android App

    Lottie is an open source animation file format that’s tiny, high quality, interactive, and can be manipulated at runtime. The top 500 apps on the App store now use Lottie to engage users and enhance conversions.A Lottie is an open-source text and vector-based file format that is easy to ship. Its cross-platform capabilities, tiny file size, and scriptable and interactive nature make it popular with designers and developers. Motion evokes emotion. It humanizes your app or website, adds empathetic experiences that entertain and engage. Lottie is the easiest way to bring motion to your apps and platforms.

    Getting Started

    Choose your Lottie

    Choose the Lottie for your Android app. You may have your own or if you don’t , you can select one from Lottie Files if you can’t find a lottie that matches your Android App , you can create your own with Adobe After Effects but this is actually quite difficult

    It’s important to test your selected Lottie using the LottieFiles app for Android to make sure the animation you’ve chosen will play the same Android as sometimes not all animations are built with features supported in the Lottie Android player.

    Just scan the QR code under the animation on LottieFiles with the app to preview the animation.

    Setup your project

    Getting started with Lottie is very straightforward. This guide assumes that you are using Android Studio as your IDE. If you’re using a different IDE, you can still use the same instructions.

    Download

    dependencies {
        def lottieVersion = "3.4.0" 
        implementation 'com.airbnb.android:lottie:$lottieVersion'
    }
    

    Bundling animations with your app

    If you need your animations to work offline, you can bundle your animations with your application by including them in your projects raw resources.

    If your project does not have one, create it by going to File>New>Folder>Raw Resources Folder. If your animation contains images, you can bundle them all together in a .zip with your .json and follow the same procedure.

    Download the animation, rename it to animation.json or animation.zip depending on your use-case, and place into your raw resources folder.

    Add LottieAnimationView to your layout

         <com.airbnb.lottie.LottieAnimationView
                    android:layout_marginEnd="20dp"
                    app:lottie_autoPlay="true"
                    app:lottie_loop="true"
                    app:lottie_rawRes="@raw/lottieAnimation"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_width="30dp"
                    android:layout_height="30dp"/>
    

    Your first Android project with Lottie animations is ready to go!. Now you can build your Android App and you can see your app look pretty good or better than use normal view. You can learn more in The official documentation for lottie-android

    That’s how you get started but there’s so much more you can do. For more tips on how to use Lottie with your projects I will mention in next Post

    Authors

    [email protected]

  • Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 2

    Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 2

    Trong lập trình với Python thì Functional Programming đóng một vai trò vô cùng quan trọng và các functions trong Python là các first-class citizens. Điều đó có nghĩa là chúng ta có thể vận hành các functions giống như các objects khác:

    • Truyền các function giống như các đối số.
    • Gán một function cho một biến số.
    • Return một function từ một function khác.

    Dựa trên những điều này, Python hỗ trợ một kỹ thuật vô cùng mạnh mẽ: closures. Sau khi hiểu closures, chúng ta sẽ đi đến tiếp cận một khái niệm rất quan trọng khác – decorators. Đây là 2 khái niệm/kỹ thuật mà bất kỳ lập trình viên Python chuyên nghiệp nào cũng cần phải nắm vững.

    Bài viết này yêu cầu kiến thức tiên quyết về scopes, namespace trong Python. Nếu bạn chưa tự tin, vui lòng đọc trước Phần 1

    Table of contents

    Free Variables and Closures

    Nhắc lại rằng: Các functions được xác định bên trong function khác có thể truy cập các biến bên ngoài (nonlocal)

    def outer():
        x = 'python'
        def inner():
            # x trỏ đến cùng một object mà biến x bên ngoài trỏ tới.
            print("{0} rocks!".format(x))
        inner()
    outer() # python rocks! --> Đây được gọi là một closure.

    Biến nonlocal x trong hàm inner được gọi là free variable. Khi chúng ta xem xét hàm inner, chúng ta thực sự đang thấy:

    • hàm inner
    • free variable x (đang có giá trị là ‘python’)

    Xin lưu ý rằng biến x trong hàm inner không thuộc local scope của hàm đó, mà nó nằm ở một nơi khác. Nhãn x này và nhãn x thuộc hàm outer liên kết lại với nhau, được gọi là closure.

    Returning the inner function

    Vậy điều gì sẽ xảy ra nếu như chúng ta không gọi hàm inner() bên trong hàm outer() mà thay vào đó, ta return nó. Khi gọi hàm outer(), hàm inner sẽ được tạo, và outer trả về hàm inner. Khi đó, closure nói trên vẫn đang còn tồn tại, chúng không bị mất đi. Vì vậy, khi gọi hàm outer(), trả về hàm inner, thực sự là chúng ta đang trả về một closure.
    Chúng ta có thể gán giá trị trả về từ hàm outer() cho một tên biến, ví dụ:

    fn = outer() # fn là closure
    fn() # python rocks!

    Khi chúng ta gọi hàm fn(), tại thời điểm đó – Python xác định giá trị của x trong một extended scope. Lưu ý rằng, hàm outer() đã chạy xong và đã kết thúc trước khi gọi hàm fn() –> scope của hàm outer đã được giải phóng. Vậy tại sao khi gọi hàm fn(), chúng ta vẫn nhận về được giá trị ‘python rocks!’ !!? –> closure.
    Thật magic! Để hiểu rõ hơn về closure, bạn hãy uống một chén trà rồi ngồi đọc tiếp nhé ;).

    Python Cells and Multi-Scoped Variables

    Xét ví dụ đơn giản sau:

    def outer():
        x = 'tuanh'
        def inner():
            print(x)
        return inner

    Giá trị của biến x được chia sẻ giữa 2 scope:

    • outer
    • closure

    Nhãn (label, name) x nằm trong 2 scope khác nhau nhưng luôn luôn refer tới cùng 1 giá trị. Python làm điều này bằng cách sử dụng một đối tượng trung gian, cell object.

    cell object đóng vai trò trung gian, và x sẽ tham chiếu gián tiếp đến đối tượng có giá trị ‘tuanh’. Trên thực tế, cả 2 biến x (trong outer và inner) đều trỏ đến cùng một cell object. Và khi chúng ta request giá trị của biến, Python thực hiện “double-hop” để lấy về giá trị cuối cùng.
    Bây giờ, chúng ta đã hiểu tại sao khi hàm outer() kết thúc, chúng ta vẫn có thể lấy được giá trị của biến x trong hàm inner rồi chứ.

    Closures

    Đến đây, chúng ta có thể nghĩ về closure như là một function + extended scope – scope mà chứa free variables.
    Giá trị của free variable là object mà cell trỏ tới. Mỗi khi function trong closure được gọi và free variable được tham chiếu:

    • Python tìm kiếm cell object, và sau đó bất kỳ cái gì cell đang trỏ tới.
    Nguồn: Fred Baptiste (Python Deep Dive – Functional)

    Introspection

    Chúng ta tiếp tục sử dụng ví dụ như trước:

    Nguồn: Fred Baptiste (Python Deep Dive – Functional)

    (more…)

  • Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 1

    Python Deep Dive: Hiểu closures, decorators và các ứng dụng của chúng – Phần 1

    Trong lập trình với Python thì Functional Programming đóng một vai trò vô cùng quan trọng và các functions trong Python là các first-class citizens. Điều đó có nghĩa là chúng ta có thể vận hành các functions giống như các objects khác:

    • Truyền các function giống như các đối số.
    • Gán một function cho một biến số.
    • Return một function từ một function khác.

    Dựa trên những điều này, Python hỗ trợ một kỹ thuật vô cùng mạnh mẽ: closures. Sau khi hiểu closures, chúng ta sẽ đi đến tiếp cận một khái niệm rất quan trọng khác – decorators. Đây là 2 khái niệm/kỹ thuật mà bất kỳ lập trình viên Python chuyên nghiệp nào cũng cần phải nắm vững.

    Table of contents

    Global and Local Scopes

    Scopes and Namespaces

    Khi một đối tượng được gán cho một biến (ví dụ: a = 100) thì biến đó trỏ đến một object nào đó và chúng ta nói rằng biến (name) đó được liên kết với đối tượng đó. Khi đó, object có thể được truy cập từ một số nơi trong code của chúng ta, sử dụng name (tên biến) nói trên.
    Tuy nhiên, hãy nhớ rằng tên biến và binding của nó (name và object) chỉ tồn tại trong một phần cụ thể của mã nguồn của chúng ta; phần mã nguồn mà ở đó name/binding được xác định – được gọi là lexical scope của các biến. Các bindings này được lưu trữ trong namespaces (mỗi scope có namespace riêng của nó).

    The Global Scope

    Global scope về cơ bản là module scope. Nó chỉ nằm trong một file .py duy nhất. Trong Python thì KHÔNG có khái niệm global scope (qua tất cả các mô đun trong toàn bộ ứng dụng) thực sự. Chỉ có một số ngoại lệ đó là có một số đối tượng built-in, được sử dụng toàn cục, chẳng hạn như: True, False, None, dict, print.

    Các biến built-in và global có thể được sử dụng bất kỳ đâu trong mô đun của chúng ta, kể cả trong các hàm.

    Global scopes được nested bên trong built-in scope.

    Nếu bạn tham chiếu một tên biến bên trong một scope và Python không tìm thấy nó trong không gian tên của scope đó –> Python sẽ tìm nó bên trong không gian tên của enclosing scope. Ví dụ trong Module1 bạn sử dụng đến nhãn True, Python sẽ tìm True bên trong không gian tên của built-in scope.

    The Local Scope

    Khi chúng ta tạo một function, chúng ta có thể tạo các tên biến bên trong function đó (sử dụng các đối số là ví dụ). Các biến được tạo bên trong function sẽ không được tạo cho đến khi function được gọi.

    Lưu ý: Giả sử chúng ta có function func1 bên trong module. Thì khi load module, Python sẽ compile mọi thứ và func1 sẽ nằm trong namespace của module đang được load. Tuy nhiên mọi thứ bên trong function sẽ chưa được tạo cho tới tận khi chúng được gọi bởi lời gọi hàm.

    def func1(a, b):
        # do something
        pass

    Mỗi khi function được gọi thì một scope mới sẽ được tạo. Và các biến được xác định bên trong function sẽ được gán cho scope đó – được gọi là function local scope. Lưu ý rằng đối tượng thực sự được tạo ra mà một biến trong hàm tham chiếu đến có thể khác nhau trong các lần hàm được gọi (đây là lý do tại sao đệ quy hoạt động!).

    Nested Scopes

    Các scope thường được nested trong các scope khác.
    Khi yêu cầu truy cập vào một object mà một biến tham chiếu đến. Ví dụ:

    print(a)

    Python sẽ tìm object được bound tới biến a như sau:

    • Đầu tiên, tìm trong local scope hiện tại. Nếu không thấy,
    • Lần lượt tìm tiếp lên các scope ‘bao bọc’ scope đang tìm.

    Thêm một ví dụ:

    # module1.py
    a = 10 # a thuộc global scope
    def myFunc(b):
        print(True) # print và True thuộc built-in scope
        print(a) # a thuộc global scope
        print(b) # b thuộc local scope
    
    myFunc(300) # một local scope mới được tạo, b trỏ đến đối tượng lưu trữ 300
    myFunc('a') # thêm một local scope nữa được tạo, b trỏ đến đối tượng lưu trữ 'a'

    The global keyword

    Khi chúng ta truy xuất một giá trị của một biến global bên trong một function. Python sẽ tìm kiến nó theo chuỗi các không gian tên tăng giần: local -> global -> built-in

    Điều gì sẽ xảy ra nếu như chúng ta sửa giá trị của một biến global bên trong một function ?

    a = 0
    def myFunc():
        a = 100 # Python sẽ hiểu rằng đây là biến local tại compile-time
        print(a)
    myFunc() ## 100
    print(a) ## 0

    Chúng ta có thể bảo Python rằng 1 biến được scoped bên trong global scope khi sử dụng nó bên trong một local scope (hàm) bằng cách sử dụng từ khóa global.

    a = 0
    def myFunc():
        global a
        a = 100
        print(a)
    myFunc() ## 100
    print(a) ## 100

    Global and Local Scoping

    Khi Python gặp một định nghĩa hàm tại compile-time, nó sẽ scan bất kỳ nhãn (biến) nào có giá trị assigned cho chúng (anywhere trong function). Nếu nhãn đó không được chỉ định là global thì nó sẽ là local. Các biến được tham chiếu nhưng not assigned một giá trị ở bất kỳ đâu trong function sẽ not be local, và Python sẽ, tại run-time, tìm kiếm chúng trong enclosing scopes.
    Ví dụ 1:

    # vidu1.py
    a = 10
    def func1():
        print(a) ## a chỉ được tham chiếu đến trong function chứ không được gán -> tại compile-time, a là non-local
    def func2():
        a = 100 ## a được gán trong function -> tại compile-time, a là local
    
    def func3():
        global a
        a = 100 ## a được gán và được chỉ định global -> tại compile-time, a là global

    Ví dụ 2:

    # vidu2.py
    def func4():
        print(a)
        a = 100 # tại compile-time, a là local
    
    # khi gọi hàm func4()
    # print(a) sẽ dẫn đến một run-time error bởi vì a là local,
    # và chúng ta đang tham chiếu đến nó trước khi chúng ta gán một
    # giá trị cho nó.
    func4()

    Code

    Tham khảo thêm các ví dụ về Global, local scopes tại:
    Click here

    Nonlocal Scopes

    Inner Functions

    Chúng ta có thể định nghĩa các hàm bên trong hàm khác, như sau:

    def outer_func():
        # some code here
        def inner_func():
            # some code here
    
        inner_func()
    outer_func()

    Cả hai hàm đều có quyền truy cập vào global scope, built-in scope và local scope tương ứng của chúng. Nhưng inner function cũng có thể truy cập vào enclosing scope của nó – ở đây là outer function.
    Scope mà không là local, cũng không là global, thì được gọi là nonlocal scope

    Modifying nonlocal variables

    Xét ví dụ sau:

    # vd.py
    def outer_func():
        x = 'TuAnh'
        def inner_func():
            x = 'HuuLinh'
        inner_func()
        print(x)
    outer_func() ## TuAnh

    Khi inner_func được compiled, python nhìn vào phép gán tới biến x -> nó xác định được biến x là local của hàm inner_func. Đứng ở góc độ inner_func thì biến x trong outer_func là nonlocal. Hai biến này trỏ đến 2 đối tượng khác nhau, vì vậy khi gọi outer_func() sẽ in ra màn hình chuỗi ‘TuAnh’ thay vì ‘HuuLinh’.

    Vậy làm cách nào để có thể sửa được biến x của hàm outer_func bên trong hàm inner_func. Rất đơn giản, giống như các biến global, chúng ta cần khai báo rõ ràng nonlocal với biến x bên trong hàm inner_func, như sau:

    def outer_func():
        x = 'TuAnh'
        def inner_func():
            nonlocal x
            x = 'HuuLinh'
        inner_func()
        print(x)
    outer_func() ## HuuLinh

    Nonlocal Variables

    Bất cứ khi nào Python nói rằng một biến là nonlocal, nó sẽ tìm kiếm trong enclosing local scopes lần lượt từ trong ra ngoài, cho tới khi bắt gặp lần đầu tên biến được chỉ định.
    Lưu ý: Python chỉ tìm trong local scopes, nó sẽ KHÔNG tìm trong global scope.

    Xem xét các ví dụ sau đây:

    # vd1.py
    def outer():
        x = 'tuanh'
        def inner1():
            x = 'python'
            def inner2():
                nonlocal x
                x = 'huulinh'
            print('inner(before): ', x)  # python
            inner2()
            print('inner(after): ', x)   # huulinh
        inner1()
        print('outer: ', x)              # tuanh
    outer()
    # vd2.py
    def outer():
        x = 'tuanh'
        def inner1():
            nonlocal x
            x = 'python'
            def inner2():
                nonlocal x
                x = 'huulinh'
            print('inner(before): ', x)  # python
            inner2()
            print('inner(after): ', x)   # huulinh
        inner1()
        print('outer: ', x)              # huulinh
    outer()
    # vd3.py
    x = 1000
    def outer():
        x = 'tuanh'
        def inner1():
            nonlocal x
            x = 'python'
            def inner2():
                global x
                x = 'huulinh'
            print('inner(before): ', x)  # python
            inner2()
            print('inner(after): ', x)   # python
        inner1()
        print('outer: ', x)              # python
    outer()
    print(x)                             # huulinh

    Authors

    [email protected]

  • SSL Pinning & Signature checking(SecureCoding – P2)

    SSL Pinning & Signature checking(SecureCoding – P2)

    SecureCoding – P2

    Hi, lại là mình, rambler coder đây

    Trong phần trước chúng ta đã tìm hiểu về MITM attack và một số thủ thuật đi kèm

    Trước khi đi vào detail rule/coding tips ở những phần sau, phần thứ 2 này mình muốn chia sẻ thêm về 1 số rule config project, runtime application để hạn chế việc application bị tấn công, sửa đổi.

    Table of contents

    Ssl Pinning

    Hành động này gần như là bắt buộc với tất các ứng dụng sử dụng giao thức HTTPS. Tuy nhiên trên Google developer page chúng ta có thể thấy một đoạn warning như sau

    Caution: Certificate Pinning is not recommended for Android applications due to the high risk of future server configuration changes, such as changing to another Certificate Authority, rendering the application unable to connect to the server without receiving a client software update.
    

    Sự lo lắng này hoàn toàn hợp lý, khi certificate hết hạn, khi thay đổi certificate..etc – không có cách nào chắc chắn người dùng sẽ cập nhật phần mềm, đồng nghĩa với việc người dùng không thể kết nối tới máy chủ, và có thể chúng ta sẽ mất một lượng user rất lớn. Nên nếu sử dụng SSL pinning, chúng ta phải handle được các case liên quan đến certification và phải tính đến khả năng force update đối với client khi có exception liên quan đến ssl certificate

    Có rất nhiều cách để triển khai SSL pinning, tuỳ thuộc cách thức mà ứng dụng kết nối với server mà chúng ta lựa chọn item phù hợp

    1. NetworkSecurityConfig

    Step 1 Create networkSecurityconfig file

    res/xml/network_security_config.xml
    

    với nội dung sau

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config>
            <domain includeSubdomains="true">example.com</domain> // your domain
            <pin-set expiration="2018-01-01">
                <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin> // your hash key 
                <!-- backup pin -->
                <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin> // your hash key 
            </pin-set>
        </domain-config>
    </network-security-config>
    

    Step2

    Khai báo sử dụng trong AndroidManifest

    <?xml version="1.0" encoding="utf-8"?>
    <manifest ... >
        <application android:networkSecurityConfig="@xml/network_security_config"
                        ... >
            ...
        </application>
    </manifest>
    

    Chi tiết vui lòng tham khảo https://developer.android.com/training/articles/security-config#CertificatePinning

    1. OKHttpClient

    Nếu đang triển khai kết nối bằng OKHttpClient thì có thể tham khảo link bên dưới https://github.com/baka3k/CleanArchitecture/blob/main/data/src/main/java/com/baka3k/architecture/data/service/base/Service.kt

    private fun buildCertificatePinner(): CertificatePinner {
            return if (pinning != null && pinning.isNotEmpty()) {
                val builder = CertificatePinner.Builder()
                for (item in pinning) {
                    builder.add(item.value, item.key)
                }
                builder.build()
            } else {
                CertificatePinner.Builder()
                    .add("api.themoviedb.org", "sha256/+vqZVAzTqUP8BGkfl88yU7SQ3C8J2uNEa55B7RZjEg0=") // your hash key (pinning SSL)
                    .build()
            }
        }
    }
    

    Sử dụng

    val pining = buildCertificatePinner()
    val okHttpClient = OkHttpClient.Builder()
                .certificatePinner(pining)
                .build()
    
    1. Hash Key? – sha256 lấy ở đâu?

    Trong 2 ví dụ ở trên chúng ta đều thấy sự xuất hiện của chuỗi ký tự sha256, có nhiều cách để lấy chuỗi này, ví dụ dùng open-ssl hoặc cách đơn giản nhất là truy cập https://www.ssllabs.com/ – Điền link muốn check vào và thông tin sẽ hiện ra như sau

    pinning

    Self signature

    Đây là một kỹ thuật mình thấy khá hiếm người sử dụng trong ứng dụng(mặc dù nó rất dễ, mất vài dòng code thôi) – dùng để ngăn chặn(phần nào đó) việc APK bị sửa đổi, thêm mã độc.

    Hầu hết lập trình viên Android đều từng ít nhất một lần tải APK từ nguồn ko chính thống, ví dụ apkpure, apkresult hoặc các game trên appstorevn

    Điều gì đảm bảo các apk này là apk sạch? ko có bị sửa đổi với mục đích thu thập thông tin người dùng, chèn quảng cáo hoặc tệ hơn là Phishing, hoặc tạo ra các attack vector để tấn công vào một hệ thống nào đó – ở đây có thể là hệ thống của chính chúng ta? – nếu đó là APK của chúng ta?

    Ví dụ, attacker có thể modify một file APK cho app ngân hàng/ví điện tử nào đó, ở giao diện đăng nhập thay vì việc gửi thông tin đăng nhập thì attacker sẽ log lại thông tin đăng nhập này, bao gồm mật khẩu, password và gửi về máy chủ của attacker chả hạn.

    Một file APK có thể được sửa đổi và sign đi sign lại nhiều lần, cách nhanh nhất để kiểm tra một file APK có bị sửa đổi rồi sign lại hay ko là check signature của file APK, signature là duy nhất, nó chỉ được gen ra bởi key dùng để sign apk và ko có cách nào từ signature trong apk sinh ngược được ra sign key cả.

    1. Dùng Keytool để lấy ra SHA của file APK release
    keytool -printcert -jarfile app-release.apk
    ... 
    MD5:  B3:4F:BE:07:AA:78:24:DC:CA:92:36:FF:AE:8C:17:DB
    SHA1: 16:59:E7:E3:0C:AA:7A:0D:F2:0D:05:20:12:A8:85:0B:32:C5:4F:68XXXX
    SHA256: 1XXXXXXXXXXXX1XXXXXXXXXXXX1XXXXXXXXXXXX1XXXXXXXXXXXX1XXXXXXXXXXXX
    
    1. Trên source code ứng dụng, khi vào một màn hình nào đó chúng ta có thể check SHA dựa vào đoạn code đơn giản bên dưới
    Signature[] sigs = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES).signatures;
    for (Signature sig : sigs)
    {
        Trace.i("MyApp", "Signature hashcode : " + sig.hashCode());
    }
    

    Compare các mã sha này => có thể phán đoán được ứng dụng có bị sửa đổi hay ko, tuỳ thuộc business, bạn có thể quyết định stop hẳn ứng dụng hoặc disable các tính năng tương ứng.

    API Key

    Đây là rule liên quan đến việc lưu trữ các API key/ User/Password cần thiết khi buid ứng dụng

    1. API Key Thông thường khi phát triển ứng dụng, developer hay có xu hướng hardcode API key trực tiếp vào source code, ví dụ thế này
    object MovieDBServer {
        const val MOVIE_DB_ACCESS_KEY = "xxxxxxxxxxxxx"
    }
    
     @GET("popular")
        suspend fun getPopular(
            @Query("api_key") clientId: String = MovieDBServer.MOVIE_DB_ACCESS_KEY
        ): MovieResult
    

    Giả sử chúng ta có 2 API key dùng cho test server và production server thì sao?

    object MovieDBServer {
        const val MOVIE_DB_ACCESS_KEY_DEBUG = "xxxxxxxxxxxxx"
        const val MOVIE_DB_ACCESS_KEY_PRODUCTION = "xxxxxxxxxxxxx"
    }
    

    Tất nhiên khi query thì phải check như thế này

    @GET("popular")
        suspend fun getPopular(
            @Query("api_key") clientId: String = if (BuildConfig.DEBUG)
                MovieDBServer.MOVIE_DB_ACCESS_KEY_DEBUG
                else MovieDBServer.MOVIE_DB_ACCESS_KEY_PRODUCTION
        ): MovieResult
    

    Có 2 issue ở đây

    • Có source code là có key, key không được quản lý tập trung
    • Nếu thêm nhiều loại key, vd stagging, production, develop..etc thì phải sửa source để build

    Để giải quyết vấn đề này, người ta sẽ đẩy việc define các key ra file – ngoài source code và sẽ inject vào trong quá trình build time, các job build khác nhau cho các môi trường khác nhau sẽ inject các API key khác nhau. Tức là thế này

    @GET("popular")
        suspend fun getPopular(
            @Query("api_key") clientId: String = BuildConfig.MOVIE_DB_ACCESS_KEY // chú ý dòng này BuildConfig.
        ): MovieResult
    

    MOVIE_DB_ACCESS_KEY được inject vào tại build time, nên có thể tuỳ chỉnh, thay đổi cho các job build khác nhau.

    Một ví dụ đơn giản, mình chọn lưu ra gradle.properties

    moviedb_access_key="PLACE YOUR KEY IN HERE"
    

    Trên file build.gradle

      defaultConfig {
            ...
            buildConfigField("String", "MOVIE_DB_ACCESS_KEY", "\"" + getMovieDBAccessKey() + "\"")
        }
    .....
    def getMovieDBAccessKey() {
        return project.findProperty("moviedb_access_key")
    }
    

    Cụ thể, nếu các bạn cần sample, thì có thể refer tại https://github.com/baka3k/CleanArchitecture/blob/main/data/build.gradle

    Tương tự đối với các sensitive information như user/password của private repository, pass của keystore..etc chúng ta cũng có thể inject trong build time như vậy

    Authors

    [email protected]

  • [Flutter] Xuất bản flutter web lên Github

    [Flutter] Xuất bản flutter web lên Github

    [Flutter] Xuất bản flutter web lên Github

    Giới thiệu

    Github có chức năng rất nay là giúp các bạn đưa trang web của mình lên hoàn toàn miễn phí. Hôm mình mình sẽ hướng dẫn các bạn cách deploy 1 trang web Flutter lên Github nhé. Cùng bắt đầu thôi nào!

    Các bước thực hiện

    Để deploy 1 trang web flutter lên github. Trước tiên chúng ta cần tạo 1 repository trên github, sau đó tạo 1 dự án flutter và đẩy nó lên github.

    Tạo repository Github

    • Các bạn vào link github.com, đăng nhập vào và nhấn New nhé

    • Ở Repository name mình sẽ điền đại 1 tên là demo_flutter_web và nhấn Create repository

    • Sau đó mình sẽ được dẫn tới link repository https://github.com/tiendung01023/demo_flutter_web. Các bạn nhớ link github này nha, tý mình sẽ dùng tới

    Xong phần khởi tạo repository git. Giờ mình đến phần flutter thôi.

    Tạo dự án flutter

    • Các bạn mở Terminal lên và tạo 1 project nha. Ở đây mình sẽ đặt tên là demo_web
    flutter create demo_web
    
    • Giờ tiếp tục tại terminal, mình cd vô dự án
    cd demo_web
    
    • Vì giờ mình quan tâm đến web, tạm thời bỏ qua mobile. Nên mình sẽ chạy lệnh build trên chrome xem trang web chạy trên local sẽ như thế nào ha

    • Chạy demo xong các bạn nhấn Ctrl + C trên Windows hoặc control + C trên MacOS để dừng demo.

    • Lưu ý: khi bạn muốn deploy web của mình lên github. Bạn cần thêm tên repository vào file như sau:

    -> Bạn mở file demo_web/web/index.html bằng bất kì trình chỉnh sửa nào

    -> Dự án github mình đặt là demo_flutter_web. Tại thẻ base bạn sửa lại thành <base href="/demo_flutter_web/"> và nhấn lưu lại

    • Tiếp theo tại terminal mình sẽ chạy câu lệnh build flutter bản web
    flutter build web
    
    • Sau khi lệnh chạy xong bạn sẽ thấy thêm thư mục demo_web/build/web. Bạn cd terminal đến thư mục này. Mình sẽ chỉ publish thư mục này lên git thôi nha.
    cd build/web
    
    • Giờ mình upload thư mục này lên github. Các bạn mở lại link github repository sẽ có phần hướng dẫn nha, hoặc các bạn chạy từng dòng lệnh như mình làm dưới đây cũng được
    git init
    git add .
    git commit -m "first commit"
    git branch -M master
    git remote add origin [email protected]:tiendung01023/demo_flutter_web.git
    git push -u origin master
    
    • Sau khi chạy hết các bạn lên repo github xem các file đã được upload lên chưa nha

    Thế là đã xong phần tạo và upload dự án flutter_web lên github. Về sau khi bạn viết thêm chức năng cho dự án, bạn cần chạy lại câu lệnh build web và push data mới lên github nha.

    Tạo page demo trang web trên github

    • Vào link github. Chọn tab Settings, xuống mục Pages

    • Trong Github Pages, bạn chọn nhánh mà muốn hiển thị trang web, hiện tại của mình thì chọn master nha. Sau đó nhấn Save

    Như hình trên Github báo đã tạo thành công trang web Your site is published at https://tiendung01023.github.io/demo_flutter_web/. Bạn bấm vào link đó để xem thành quả nha

    Kết thúc

    Hi vọng qua bài viết của mình sẽ giúp bạn sáng tạo thêm 1 số trò với flutter web này nha

    Cảm ơn các bạn đã xem bài viết.

    Tác giả

    Phạm Tiến Dũng [email protected]

  • Review Source code

    Review Source code

    Review source code.

    Gần đây khi mình tham gia các buổi phỏng vấn Senior thì nhận ra một điều, hầu hết các bạn "Senior" đều có note trong CV rằng "review source code" là một phần công việc của bạn ý, có điều khi mình hỏi các bạn review source code như thế nào thì đều chỉ nói được rất chung chung theo kiểu: kiểm tra source code có dễ đọc ko, có đúng logic trong file specs ko..etc, rất hiếm người trả lời một cách bài bản.

    Vậy rốt cuộc " Review source code" là task thế nào hoặc chúng ta nên trả lời cái gì khi bị hỏi: review source là review cái gì?

    Table of contents

    Rule khi review source code

    1. Review dựa trên check list.

      Bạn luôn luôn phải xây dựng một bộ check list, một bộ quan điểm cho dòng dự án mình đang tham gia. Mỗi dòng dự án sẽ có độ ưu tiên khác nhau cho từng mục trong checklist, sẽ có những mục là mandatory với dự án này nhưng là optional với dự án khác.

      Check list này phải được training cho dev trong dự án để hiểu từng item một. Điều này hạn chế việc lack quan điểm review, hạn chế các sai lầm trong những lúc mệt mỏi kém tỉnh táo trong ngày làm việc căng thẳng, hạn chế sự xung đột của reviewer với developer.

    2. Mọi quan điểm đúng/sai đều phải dựa trên offical document nào đó.

      Nếu là đúng sai về mặt requirement thì phải có requirement chứng minh lỗi sai của developer, nếu là đúng sai về mặt coding, framework thì phải chỉ ra được lỗi quy định trên ngôn ngữ lập trình hoặc official document của framework.

      Tuyệt đối ko dựa vào "Kinh nghiệm cá nhân" để áp đặt tư tưởng bản thân xuống team. Thậm chí kể cả coding convention cũng phải có document để refer.

    Một số quan điểm review

    1. Coding convention

    Hầu hết các ngon ngữ lập trình, các dòng framework đều có các page define rõ các rule liên quan đến convention, ví dụ:

    https://source.android.com/setup/contribute/code-style

    https://developer.android.com/kotlin/style-guide

    https://dart.dev/guides/language/effective-dart/style

    Việc cần làm của chúng ta là đọc hiểu rồi tuân theo. Điều này khiến source code của dự án trở lên "thân thiện" với người đọc hơn, có vẻ chuyên nghiệp hơn. Bạn đã làm quen với các project chuẩn coding convention mà chuyển sang đọc source một bạn sinh viên lần đầu đi làm, hoặc các bạn làm theo kiểu free style sẽ cảm thấy rất khác biệt, đôi khi có follow coding convention hay không lại là cơ sở để đánh giá liệu rằng coder đã code đủ lâu, đã có kinh nghiệm đủ lâu với lĩnh vực mà họ đang làm hay chưa.

    2. Design

    Quan điểm review liên quan đến design là một quan điểm rất rộng và tốn giấy mực, nó không chỉ đơn thuần việc các module/class làm việc với nhau thế nào, tạo interface ra sao, abstraction ổn chưa..etc mà còn là việc liên quan đến việc thiết kế bao nhiêu luồng chạy cho ứng dụng là đủ, context để các task chạy có đủ lâu, đủ ổn định không, các component đã đủ yêu cầu liên quan đến security chưa… và nhiều thứ phức tạp liên quan nữa

    • Security: Secure coding, việc tổ chức source code thế nào, trong code lưu các api key ra sao, có bao nhiêu module dữ liệu được đi ra ngoài, cơ chế lưu dữ liệu, mã hoá dữ liệu, cơ chế đảm bảo an toàn cho các component khi bị gọi đến, quan điểm này thật sự dài, có thể mình sẽ viết trong các seri tiếp theo tại đây (https://magz.techover.io/2021/08/02/giao-thuc-bao-mat-https-va-mitm-attacksecure-coding-p1/)
    • Performance: Bao nhiêu thread là đủ, có cần pool thread không? đã giải phóng các resource chưa, dùng loại layout này đã tối ưu chưa, dùng loop nào để optimize tốc độ, có cần cache không, dùng cấu trúc dữ liệu nào để optimize đoạn xử lý này? task này chạy trên worker thread hay main thread..etc
    • Cấu trúc: có cực kỳ nhiều thứ để check, vd: sử dụng pattern nào, có nên dùng DI ko, đoạn source này nên do class/moduel nào xử lý..etc Mình rất dị ứng cụm từ "dựa vào kinh nghiệm" nhưng thực sự quan điểm này phần lớn do đến từ kinh nghiệm, đại khái thì chúng ta sẽ quan tâm đến high cohesion, low coupling, tạo ra các boundaries – các ranh giới cho class/ module của chương trình

    3. Mistake liên quan đến Logic code/requirement

    Đây chính là phần mà hầu hết các bạn đi phỏng vấn sẽ trả lời vào, đại khái là check xem có đúng requirement ko, code logic có nhầm đoạn nào ko? có null rồi bị bug ko..etc

    Review thử một đoạn source

    Rồi,đó là lý thuyết, giờ chúng ta thử áp dụng để review đoạn source bên dưới

    public class BroadCastTestJava extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            decodeImage(intent.getStringExtra("imageData"));
            new MyAsyncTask("A").execute();
            new MyAsyncTask("B").executeOnExecutor();
        }
    
        private Bitmap decodeImage(String image) {
            byte[] imgBytes = Base64.decode(image, 0);
            return BitmapFactory.decodeByteArray(imgBytes, 0, imgBytes.length);
        }
    
        public class MyAsyncTask extends AsyncTask {
            private String name;
            public MyAsyncTask(String name) {
                this.name = name;
            }
            @Override
            protected Object doInBackground(Object[] objects) {
                for (int i = 0; i < 10; i++) {
                    Log.d("test", "My name is " + this.name + " " + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }
        }
    

    1. Dùng Tool check.

    Đa số các IDE đều hỗ trợ, ví dụ android studio thì có thể làm như dưới Check by Tool

    Hầu hết các lỗi do tool chỉ ra, đều đúng là lỗi thật và có thể fix được, nên đương nhiên, chúng ta sẽ xử lý nhé 😛

    2. Coding convention Anh em thử xem có các lỗi convention nào :p

    3. Design Có thể thấy có các issue sau:

    • decodeImage() không được chạy trên main thread (task vụ nặng)

    • public class phải chuyển thành private static class(Trong java inner class sẽ refer sang outer class -> leak memory)

    • Không được dùng asynctask trên BroadcastReceiver, context của broadcast không đủ dài để xử lý background, nên phải đưa về context sống lâu hơn, ví dụ service để xử lý tiếp. Đây là vấn đề liên quan đến thiết kế các task chạy ở đâu cho phù hợp. Hoặc nếu task ngắn hạn thì có thể dùng goAsync(https://developer.android.com/reference/android/content/BroadcastReceiver#goAsync())

    • Security: Liệu rằng intent gửi đến(để ra lệnh cho app decode image) có phải intent của app ko? có đúng là được phép để xử lý không? hay do attacker đang cố tình tấn công vào app? liệu rằng Broadcast có được bảo vệ bởi permission không? – phần này mình sẽ break sang một bài viết khác liên quan đến Secure Coding

    4. Logic code

    • decodeImage không verify intent đầu vào/ không handle exception, giả sử intent nhả ra null hoặc rỗng thì sẽ có exception xảy ra, có thể app sẽ crash??

    => gọi coder ra "nhờ" bạn ý fix thôi

    Việc review code easy hơn rồi – đúng ko nào. Chắc sẽ còn nhiều lỗi nữa, anh em thử check tiếp nhé.

    Tổng kết lại:

    1. Chúng ta nên có 1 bộ checklist dùng để review(có thể tuỳ vào dòng dự án, mức độ khó tính của khách hàng, mức độ chặt chẽ dự án yêu cầu)- để hạn chế các mistake về mặt con người
    2. Các quan điểm review đúng/sai về mặt kỹ thuật phải thuần tuý dựa vào quan điểm kỹ thuật, official document để quyết định đúng/sai, không được áp đặt dựa vào "kinh nghiệm cá nhân"

    Code tiếp thôi nào !

    guru

    Authors

    [email protected]

  • [DesignPattern] Simple Factory Pattern

    [DesignPattern] Simple Factory Pattern

    Background

    Hầu hết anh em developer đều đã nghe qua về Design Pattern

    Tuy nhiên mình thấy còn nhiều người (gồm cả mình) đều không có kinh nghiêm áp dụng nó.

    Nên mình lập topic về design pattern để mọi người có thể chia sẻ kinh nghiệm và cách áp dụng nó trong các bài toán cụ thể.

    Vậy đầu tiên chúng ta phải hiểu design pattern là gì?

    • Theo mình hiểu design pattern đơn giản là các giải pháp mẫu tối ưu cho từng tình huống cụ thể trong lập trình OOP.

    Taị sao lại sử dụng design pattern?

    Một thực tế là mình nghe rất nhiều câu như refactor đi (dm code như shit, fucking coding …),đập hết đi xây lại.

    Tại sao chúng ta lại muốn như thế?

    Vì hầu hết các source code ban đầu đều rất khó maintain và mở rộng, nên khi có một yêu cầu mới hay một bug,

    anh em lại cặm cụi sửa code, sửa bug này lại sinh ra bug khác nên effort để giải quyết một vấn đề rất tốn kém.

    Do đó nếu mình không chỉ "code để chạy được" mà suy nghĩ, áp dụng các design pattern trươc khi viết ra

    sẽ rút ngắn phần lớn thời gian development và maintain.

    Sau đây mình xin trình bầy một số design pattern mà mình đã đọc qua.

    Đầu tiên mình xin tập trung vào một loại pattern mà chắc anh em ai cũng nghe qua, đó là Factory Pattern

    Factory Pattern có 3 loại: Simple factory, Factory method và Abstract Factory

    Bài lần này mình sẽ trình bày về Simple Factory Pattern

    Simple Factory Pattern

    Bài toán

    Nào chúng ta hãy làm quen với Simple Factory Pattern với một ví dụ như sau:

    Giả sử bạn đang viết chương trình đặt hàng và ship hàng cho một cửa hàng Pizza

    Quá trình đặt hàng một chiếc Pizza sẽ qua các công đoạn như chuẩn bị (prepare), nướng (bake) và đóng hộp (box)

    Xử lý để chạy được & vấn đề

    Chương trình có thể được viết như sau

    Class PizzaStore {
      public Pizza orderPizza() {
        Pizza pizza = new Pizza();
        pizza.prepare();
        pizza.bake();
        pizza.box();
        return pizza;
      }
      public void shipPizza() {
        Pizza pizza = new Pizza();
        pizza.ship();
       }
    }
    

    OK như vậy là có sample về chương trình orderPizza và shipPizza.

    Nhưng chủ cửa hàng lại muốn có nhiều món pizza cơ mà.

    Ví du: Pizza gà (ChickenPizza), Pizza phô mai(CheesePizza)

    Bạn nên làm thế nào ???

    Dễ mà tạo một lớp cha Pizza và create 2 lớp con là ChickenPizza và CheesePizza

    Sau đó add thêm parameter type cho orderPizza và shipPizza

    Class PizzaStore {
      public Pizza orderPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.box();
        return pizza;
      }
      public void shipPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.ship();
      }
    }
    

    OK các bạn thấy thế nào, chương trình chạy ngon không có lỗi gì luôn :D.

    Một tuần sau cửa hàng thấy cần thêm món Pizza hải sản (SeaFoodPizza) để tăng thêm khách hàng

    Bạn sẽ vào sửa method orderPizza???

    Class PizzaStore {
      public Pizza orderPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        } else if ("seafood".equals(type) {
          pizza = new SeaFoodPizza();
        }
        pizza.prepare();
        pizza.bake();
        pizza.box();
      }
      public void shipPizza(String type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        }
        pizza.ship();
      }
    }
    

    Cơn ác mộng mới chỉ bắt đầu :)). Đấy là mình chỉ liệt kê 2 chức năng cơ bản, điều gì sẽ xảy ra nếu còn rất nhiều chức năng khác cần lấy thông tin pizza theo từng loại

    Ví du: Lấy thông tin giá, tên, … của từng loại Pizza

    Bạn sẽ phải hì hục sửa code tất cả cả các method đấy nếu cửa hàng tạo thêm một loại Pizza.

    Vâng bạn sẽ vẫn cố gắng sửa để nó có thể chạy được nhưng bạn sẽ tốn rất nhiều effort để làm việc này nếu có hàng chục method cần sửa.

    Bạn cũng lo lắng mình có thể quên xử lý ở một method nào đấy,…

    -> Giờ bạn đã sợ maintain chưa

    Áp dụng Simple Factory Pattern vào bài toán

    Quá nhiều điều để lo lắng chúng ta phải refactor thôi.

    Đầu tiên chắc các bạn cũng thấy luôn, chúng ta cần đóng gói việc khởi tạo object bên dưới

    if ("chicken".equals(type) {
      pizza = new ChickenPizza();
    } esle if ("cheese".equals(type) {
      pizza = new ChickenPizza();
    } else if ("seafood".equals(type) {
      pizza = new SeaFoodPizza();
    }
    

    Chuyển đoạn code trên sang một class factory

    Class SimplePizzaFactory {
      public Pizza createPizza(type) {
        Pizza pizza;
        if ("chicken".equals(type) {
          pizza = new ChickenPizza();
        } esle if ("cheese".equals(type) {
          pizza = new ChickenPizza();
        } else if ("seafood".equals(type) {
          pizza = new SeaFoodPizza();
        }
        return pizza;
     }
    }
    

    Tiếp theo chúng ta sẽ sử dụng SimplePizzaFactory để tạo trong PizzaStore để tạo các object

    Class PizzaStore {
      SimplePizzaFactory mFactory;
    
      public PizzaStore (SimplePizzaFactory factory) {
        mFactory = factory;
      }
    
      public Pizza orderPizza(String type) {
        Pizza pizza = mFactory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.box();
      }
      public void shipPizza(String type) {
        Pizza pizza = mFactory.createPizza(type);
        pizza.ship();
      }
    }
    

    Bạn thấy công việc đơn giản hơn chưa, việc khởi tạo object cần thiết được xử lý trong Factory

    Như vậy chúng ta không cần phải lo lắng sửa code ở từng method như orderPizzahay shipPizza, … nữa 🙂

    Conlution

    Đây chỉ là một ví dụ rất cơ bản về Design Pattern giúp mọi người dễ hình dung và tiếp cận

    Chúng ta còn rất nhiều bài toán phức tạp khác cần Design Pattern để xử lý

    Quan trọng mọi người hiểu được việc "code để chạy" có thể nhanh trong thời điểm đấy

    Tuy nhiên việc mọi người phải rework để phát triển nó sẽ tốn gấp đôi gấp mười lần effort nếu mọi người có thể làm Design cẩn thận từ đầu

    Do đó việc hiểu các Design pattern hỗ trợ rất tốt cho mọi người trong việc triển khai các bài toán cụ thể

    Hy vọng bài này giúp mọi người hiểu được cơ bản Design Pattern là gì và tầm quan trọng của nó trong software

  • [Flutter] Hướng dẫn tạo plugin và gọi thư viện native (Phần cuối)

    [Flutter] Hướng dẫn tạo plugin và gọi thư viện native (Phần cuối)

    Xem thêm Phần 1-2 Xem thêm Phần 3

    Phần 4: Hướng dẫn thêm thư viện native

    Trong phần này, mình sẽ demo việc gửi 1 DateTime từ flutter xuống native code để kiểm tra xem có phải ngày hiện tại hay không? Mình sẽ sử dụng thư viện Tempo của tác giả cesarferreira cho Android và SwiftDate của tác giả Daniele Margutti cho iOS.

    Vì flutter và native không giao tiếp với nhau bằng biến loại DateTime được, nên mình sẽ cần chuyển DateTime sang dạng string UTC để xử lý nhé.

    Thêm code flutter để hiển thị kết quả

    Trong file lib/src/sample_call_native.dart các bạn thêm 1 hàm như sau:

      static Future isToday(DateTime dateTime) async {
        final date = dateTime.toUtc().toIso8601String();
        final bool? isSuccess = await _channel.invokeMethod(
          'isToday',
          {
            'dateTime': date,
          },
        );
        return isSuccess;
      }
    

    Trong file example/lib/main.dart bạn đổi lại code như sau:

    import 'package:flutter/material.dart';
    import 'package:sample_plugin_flutter/sample_plugin_flutter.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() =&gt; _MyAppState();
    }
    
    class _MyAppState extends State {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  /// Phần 2. Hướng dẫn tạo Widget với plugin
                  SampleButton(
                    text: "Sample Button",
                    onPressed: () {
                      print("Sample Button Click");
                    },
                  ),
    
                  /// Phần 3. Hướng dẫn gọi native code từ plugin
                  FutureBuilder(
                    future: SampleCallNativeFlutter.platformVersion,
                    builder: (_, snapshoot) {
                      return Text(snapshoot.data ?? '');
                    },
                  ),
    
                  /// Phần 4. Hướng dẫn gọi native code từ plugin
                  FutureBuilder(
                    future: SampleCallNativeFlutter.isToday(DateTime.now()),
                    builder: (_, snapshoot) {
                      return Text('isToDay: ${DateTime.now()} is ${snapshoot.data}');
                    },
                  ),
                  FutureBuilder(
                    future: SampleCallNativeFlutter.isToday(DateTime(2021,01,01)),
                    builder: (_, snapshoot) {
                      return Text('isToDay: ${DateTime(2021,01,01)} is ${snapshoot.data}');
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    Thêm thư viện cho iOS

    Thường khi thêm 1 thư viện vào code iOS, bạn cần sử dụng Cocoapods thêm nó vào Podfile. Nhưng với plugin thì bạn sẽ thêm dependency nó vào ios/sample_plugin_flutter.podspec.

    File này cũng giúp bạn khai báo s.static_framework = true(1 số thư viện native cần phải khai báo biến này) hay s.ios.deployment_target = ‘9.0’ (để giới hạn version build iOS).

    (Nếu bạn chưa biết Cocoapods là gì, bạn có thể tham khảo tại đây)

    #
    # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
    # Run `pod lib lint sample_plugin_flutter.podspec` to validate before publishing.
    #
    Pod::Spec.new do |s|
      s.name             = 'sample_plugin_flutter'
      s.version          = '0.0.1'
      s.summary          = 'A new flutter plugin project.'
      s.description      = &lt; '../LICENSE' }
      s.author           = { 'Your Company' =&gt; '[email protected]' }
      s.source           = { :path =&gt; '.' }
      s.source_files = 'Classes/**/*'
      s.dependency 'Flutter'
      s.dependency 'SwiftDate' # Khai báo thư viện iOS tại đây
      s.platform = :ios, '8.0'
    
      # Flutter.framework does not contain a i386 slice.
      s.pod_target_xcconfig = { 'DEFINES_MODULE' =&gt; 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' =&gt; 'i386' }
      s.swift_version = '5.0'
    end
    

    Sau đó bạn cần chạy pod install cho thư mục example/ios và vào Xcode chọn menu Product/Clean Build Folder. Trong file SwiftSamplePluginFlutterPlugin bạn đổi lại code như sau:

    import Flutter
    import UIKit
    import SwiftDate
    
    public class SwiftSamplePluginFlutterPlugin: NSObject, FlutterPlugin {
      public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "sample_plugin_flutter", binaryMessenger: registrar.messenger())
        let instance = SwiftSamplePluginFlutterPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel)
      }
    
      public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "getPlatformVersion":
            result("iOS " + UIDevice.current.systemVersion)
        case "isToday":
            isToday(call, result)
        default:
            result(nil)
        }
      }
        
        private func isToday(_ call: FlutterMethodCall,_ result: @escaping FlutterResult) {
            let arguments = call.arguments as! Dictionary
            let dateTime = arguments["dateTime"] as! String;
            // Convert to local
            let localDate = dateTime.toDate(nil, region: Region.current)
            // Check isToday
            let checkToday = localDate?.isToday
            result(checkToday)
        }
    }
    

    Thế là xong bên iOS, giờ qua phần của Android.

    Thêm thư viện cho Android

    Trong Gradle Scripts/build.gradle(Module: android.sample_plugin_flutter) bạn thêm dòng bên dưới ở cuối file và nhấn Sync now

    dependencies {
      implementation 'com.github.cesarferreira:tempo:+'
    }
    

    Sample 5

    Trong file android/src/main/kotlin/com/example/sample_plugin_flutter/SamplePluginFlutterPlugin.kt bạn đổi lại code như sau:

    package com.example.sample_plugin_flutter
    
    import androidx.annotation.NonNull
    import com.cesarferreira.tempo.Tempo
    import com.cesarferreira.tempo.isToday
    
    import io.flutter.embedding.engine.plugins.FlutterPlugin
    import io.flutter.plugin.common.MethodCall
    import io.flutter.plugin.common.MethodChannel
    import io.flutter.plugin.common.MethodChannel.MethodCallHandler
    import io.flutter.plugin.common.MethodChannel.Result
    import java.text.SimpleDateFormat
    import java.util.*
    
    /** SamplePluginFlutterPlugin */
    class SamplePluginFlutterPlugin: FlutterPlugin, MethodCallHandler {
      /// The MethodChannel that will the communication between Flutter and native Android
      ///
      /// This local reference serves to register the plugin with the Flutter Engine and unregister it
      /// when the Flutter Engine is detached from the Activity
      private lateinit var channel : MethodChannel
    
      override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sample_plugin_flutter")
        channel.setMethodCallHandler(this)
      }
    
      override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
          "getPlatformVersion" -&gt; result.success("Android ${android.os.Build.VERSION.RELEASE}")
          "isToday" -&gt; isToday(call, result)
          else -&gt; {
            result.notImplemented()
          }
        }
      }
    
      private fun isToday(@NonNull call: MethodCall, @NonNull result: Result) {
        var arguments = call.arguments as Map
        var dateTime = arguments["dateTime"] as String
        var localDate = dateTime.toDate()
        var checkToday = localDate.isToday // library Tempo check isToday
        result.success(checkToday)
      }
    
      private fun String.toDate(dateFormat: String = "yyyy-MM-dd'T'HH:mm:ss", timeZone: TimeZone = TimeZone.getTimeZone("UTC")): Date {
        val parser = SimpleDateFormat(dateFormat, Locale.getDefault())
        parser.timeZone = timeZone
        return parser.parse(this)
      }
    
      override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
      }
    }
    

    Xong rồi, giờ chạy flutter run để xem thành quả cuối cùng thôi nào.

    Sample 6

    Kết thúc

    Hi vọng qua bài viết của mình giúp ích cho các bạn phần nào việc làm qua viết plugin cho Flutter. Mình để link Github ở đây để các bạn tham khảo nha.

    Nguồn tham khảo:

    Bài viết đầy đủ tại Viblo Cảm ơn các bạn đã xem bài viết.

    Tác giả

    Phạm Tiến Dũng [email protected]

  • [Flutter] Hướng dẫn tạo plugin và gọi thư viện native (Phần 3)

    [Flutter] Hướng dẫn tạo plugin và gọi thư viện native (Phần 3)

    Xem lại Phần 1-2

    Phần 3. Hướng dẫn gọi native code từ plugin

    1. Làm việc với IDE native

    Khi làm việc với native code, bạn nên dùng Android Studio khi code Android và Xcode khi code iOS nhé. 2 IDE này sẽ hỗ trợ bạn tốt hơn trong việc báo lỗi và cả debug code.

    • Trong Android Studio bạn mở thư mục example/android/, giao diện cây thư mục trong IDE sẽ như thế này. Sample 2

    • Trong Xcode bạn mở thư mục example/ios/Runner.xcworkspace, giao diện cây thư mục trong IDE sẽ như thế này. Sample 3

    2. Code native cho plugin

    Để gọi native code, bạn sẽ cần sử dụng channel, thường channel nên được đặt cùng tên với tên plugin của bạn. Thông qua channel chúng ta sẽ gọi hàm native và nhận kết quả từ đó.

    Các bạn có thể tham khảo mapping các loại biến giữa các nền tảng tại đây.

    Trong thư mục lib/src các bạn tạo 1 file dart mới và đặt tên là sample_call_native.dart. File này sẽ tạo MethodChannel(‘sample_plugin_flutter’) để liên kết đến native code và hàm platformVersion() để kiểm tra version của thiết bị người dùng.

    import 'dart:async';
    
    import 'package:flutter/services.dart';
    
    class SampleCallNativeFlutter {
      static const MethodChannel _channel =
          const MethodChannel('sample_plugin_flutter');
    
      static Future get platformVersion async {
        final String? version = await _channel.invokeMethod('getPlatformVersion');
        return version;
      }
    }
    

    Trong file lib/src/src.dart các bạn thêm dòng export.

    export 'sample_call_native.dart';
    

    Trong file android/src/main/kotlin/com/example/sample_plugin_flutter/SamplePluginFlutterPlugin.kt đã code demo sẵn channel và cách trả về platformVersion như minh họa phía dưới. Tại hàm onMethodCall, cần kiểm tra tên call.method được gọi là gì và trả về cho flutter kết quả thông qua result.success().

    Lưu ý: nếu bạn gọi 1 function không cần trả kết quả, bạn vẫn phải gọi result.success(null) để báo về cho flutter biết hàm đã thực hiện xong.

    package com.example.sample_plugin_flutter
    
    import androidx.annotation.NonNull
    
    import io.flutter.embedding.engine.plugins.FlutterPlugin
    import io.flutter.plugin.common.MethodCall
    import io.flutter.plugin.common.MethodChannel
    import io.flutter.plugin.common.MethodChannel.MethodCallHandler
    import io.flutter.plugin.common.MethodChannel.Result
    
    /** SamplePluginFlutterPlugin */
    class SamplePluginFlutterPlugin: FlutterPlugin, MethodCallHandler {
      /// The MethodChannel that will the communication between Flutter and native Android
      ///
      /// This local reference serves to register the plugin with the Flutter Engine and unregister it
      /// when the Flutter Engine is detached from the Activity
      private lateinit var channel : MethodChannel
    
      override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
        channel = MethodChannel(flutterPluginBinding.binaryMessenger, "sample_plugin_flutter")
        channel.setMethodCallHandler(this)
      }
    
      override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        when (call.method) {
          "getPlatformVersion" -&gt; result.success("Android ${android.os.Build.VERSION.RELEASE}")
          else -&gt; {
            result.notImplemented()
          }
        }
      }
    
      override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
        channel.setMethodCallHandler(null)
      }
    }
    
    

    Tương tự trong file ios/Classes/SwiftSamplePluginFlutterPlugin.swift đã code demo sẵn channel và cách trả về platformVersion như minh họa phía dưới. Tại hàm handle, cần kiểm tra tên call.method được gọi là gì và trả về cho flutter kết quả thông qua result(). Nếu bạn gọi 1 function không cần trả kết quả, bạn vẫn cần gọi result(nil) để báo về cho flutter biết hàm đã thực hiện xong.

    import Flutter
    import UIKit
    
    public class SwiftSamplePluginFlutterPlugin: NSObject, FlutterPlugin {
      public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "sample_plugin_flutter", binaryMessenger: registrar.messenger())
        let instance = SwiftSamplePluginFlutterPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel)
      }
    
      public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "getPlatformVersion":
            result("iOS " + UIDevice.current.systemVersion)
        default:
            result(nil)
        }
      }
    }
    

    Trong file example/lib/main.dart bạn đổi lại code như sau:

    import 'package:flutter/material.dart';
    import 'package:sample_plugin_flutter/sample_plugin_flutter.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() =&gt; _MyAppState();
    }
    
    class _MyAppState extends State {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  /// Phần 2. Hướng dẫn tạo Widget với plugin
                  SampleButton(
                    text: "Sample Button",
                    onPressed: () {
                      print("Sample Button Click");
                    },
                  ),
    
                  /// Phần 3. Hướng dẫn gọi native code từ plugin
                  FutureBuilder(
                    future: SampleCallNativeFlutter.platformVersion,
                    builder: (_, snapshoot) {
                      return Text(snapshoot.data ?? '');
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    Chạy flutter run để xem kết quả thôi nào.

    Sample 4

    Còn tiếp

    Bài viết đầy đủ tại Viblo

  • [Flutter] Hướng dẫn tạo plugin và gọi thư viện native

    [Flutter] Hướng dẫn tạo plugin và gọi thư viện native

    Giới thiệu

    Hiện nay tài liệu cho việc tạo plugin cho flutter khá ít, mà tài liệu để plugin gọi xuống các thư viện native càng hiếm hơn nữa. Nên hôm nay mình viết bài này để giúp mọi người dễ dàng hơn trong việc viết plugin với flutter. Cùng bắt đầu thôi nào!

    Phần 1. Hướng dẫn tạo plugin

    Để tạo 1 plugin bạn cần dùng lệnh flutter create –template=plugin

    • Sử dụng tùy chọn –platforms để chỉ định plugin sẽ có những ngôn ngữ nào. Có các tùy chọn như: android, ios, web, linux, macos, windows
    • Sử dụng tùy chọn –org để chỉ định tên miền cho tổ chức của bạn
    • Sử dụng tùy chọn –a để chỉ định ngôn ngữ cho android. Bạn có thể chọn java hoặc kotlin
    • Sử dụng tùy chọn –i để chỉ định ngôn ngữ cho ios. Bạn có thể chọn swift hoặc objc
    • Và cuối cùng sẽ là tên plugin của bạn

    Tham khảo:

    flutter create --org com.example --template=plugin --platforms=android,ios -a kotlin -i swift sample_plugin_flutter
    

    Sau khi thao tác trên bạn sẽ có 1 plugin trong thư mục sample_plugin_flutter với một số file cơ bản sau:

    • lib/sample_plugin_flutter.dart => API Dart cho plugin. File này dùng để kết nối các thành phần của plugin, kết nối với native code
    • android/src/main/kotlin/com/example/sample_plugin_flutter/SamplePluginFlutterPlugin.kt => Triển khai API plugin trong Kotlin dành riêng cho nền tảng Android.
    • ios/Classes/SwiftSamplePluginFlutterPlugin.swift => Triển khai API plugin trong Swift dành riêng cho nền tảng iOS.
    • example/ => Một ứng dụng Flutter phụ thuộc vào plugin và minh họa cách sử dụng nó.
    • lib/src/ => Thư mục này sẽ không có sẵn, nhưng bạn cần tạo thư mục này để chứa các file private. Bạn chỉ public các file cần thiết thông qua khai báo export trong lib/sample_plugin_flutter.dart

    Phần 2. Hướng dẫn tạo Widget với plugin

    Để tạo Widget hay Function để người dùng plugin để thể gọi và sử dụng, bạn cần đưa file đó vào thư mục src và export nó ra ngoài. Khi làm vậy, người dùng chỉ cần import 1 dòng duy nhất là có thể sử dụng plugin của bạn.

    Trong thư mục lib/src các bạn tạo 1 file dart mới và đặt tên là sample_button.dart

    import 'package:flutter/material.dart';
    
    class SampleButton extends StatelessWidget {
      final String text;
      final VoidCallback? onPressed;
    
      const SampleButton({
        Key? key,
        required this.text,
        this.onPressed,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return TextButton(
          onPressed: onPressed,
          child: Container(
            padding: EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.blue,
              borderRadius: BorderRadius.circular(10),
            ),
            child: Text(
              text,
              style: TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        );
      }
    }
    

    Trong thư mục lib/src các bạn tạo thêm file src.dart, file này sẽ chứa tất cả file mà bạn muốn export ra ngoài.

    export 'sample_button.dart';
    

    Trong file lib/sample_plugin_flutter.dart bạn nên xóa hết code mặc định đi. File này các bạn sẽ chứa những file bạn muốn export hoặc export những plugin khác có trong dependence của bạn.

    export 'src/src.dart';
    

    Giờ thì thử build Widget này lên từ app example nhé. Trong file example/lib/main.dart bạn đổi lại code như sau:

    import 'package:flutter/material.dart';
    import 'package:sample_plugin_flutter/sample_plugin_flutter.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() =&gt; _MyAppState();
    }
    
    class _MyAppState extends State {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  /// Phần 2. Hướng dẫn tạo Widget với plugin
                  SampleButton(
                    text: "Sample Button",
                    onPressed: () {
                      print("Sample Button Click");
                    },
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

    Chạy flutter run để xem kết quả thôi nào.

    Sample 1

    Còn tiếp

    Bài viết đầy đủ tại Viblo