Month: March 2020

  • Sự khác biệt giữa Struct và Class

    Sự khác biệt giữa Struct và Class

    Class và Struct là những thứ bất cứ 1 lập trình viên cũng thường xuyên sử dụng. Tuy nhiên, đối với những người mới thì việc phân biệt giữa class và struct là vẫn còn mơ hồ.
    Ở bài viết này, mình sẽ nói về các điểm khác nhau giữa Class và Struct.

    Nội dung bài viết:

    • Struct và Class là gì?
    • Điểm giống nhau giữa Struct và Class
    • Điểm khác nhau giữa Struct và Class
    • Khi nào nên sử dụng struct / class

    Struct và Class là gì?

    Struct và class là các cấu trúc linh hoạt, được sử dụng với nhiều mục đích khác nhau để trở thành các khối xây dựng chương trình của ban. Bạn định nghĩa các thuộc tính và phương thức để thêm vào các struct/class của bạn.

    Cách khởi tạo 1 struc và 1 class khá giống nhau.

    Điểm giống nhau giữa struct và class:

    Struct và Class đều có thể:

    • Định nghĩa, khai báo các thuộc tính và hàm.
    • Khai báo subscripts.
    class Rank {
        subscript (index: Int) -> String {
            switch index {
                case 1: return "First"
                case 2: return "Second"
                case 3: return "Three"
                default: return "Dont have rank"
            }
        }
    }
    
    let rank = Rank()
    print (rank[1]) // -> "First"
    print (rank[12]) // -> "Dont have rank"
    • Khai báo các initializers để khởi tạo.
    • Có thể mở rộng bằng extension.
    • Có thể implement các protocol để cung cấp các chức năng tiêu chuẩn.

    Điểm khác nhau giữa Struct và Class:

    Initialize:

    Khi định nghĩa 1 class, bạn bắt buộc phải khởi tạo 1 hàm init cho các thuộc tính không phải optional hoặc chưa có giá trị default.

    class Car {
        let id: Int = 1
        var color: UIColor?
        var price: Double
        
        init(price: Double) {
            self.price = price
        }
    }
    
    let car1 = Car(price: 5000)

    Còn khi định nghĩa 1 struct, bạn không cần phải khởi tạo 1 hàm init bởi khi đó Struct đã tự định nghĩa 1 hàm init default cho bạn.

    struct Car {
        let id: Int = 1
        var color: UIColor
        var price: Double
    }
    
    let car1 = Car(color: .red, price: 5000)

    Tuy nhiên, nếu bạn khai báo thêm các init khác thì hàm init default của struct sẽ bị mất.
    Vì vậy, để tránh điều này, chỉ cần khai báo các hàm init mới ở extension thì hàm init default sẽ không bị mất.

    Struct là Value types còn Class là Reference types

    Value type: 1 instance có kiểu là value type thì nó sẽ tự tạo ra các bản copy các giá trị của mình để truyền đi mỗi khi được nó đươc dùng để gán cho các instance khác, hoặc khi được dùng để truyền vào hàm. Bởi vậy, nếu bạn thay đổi giá trị các bản copy thì giá trị bản gốc cũng sẽ không bị thay đôi:

    let car1 = Car(price: 5000)
    var car2 = car1
    car2.price = 10000
    print(car1.price) // -> 5000.0
    print(car2.price) // -> 10000.0

    Reference type: Thay vì việc tạo ra các bản sao, thì 1 instance kiểu reference type sẽ tự truyền đi 1 tham chiếu tới chính nó khi được gán cho các insstance khác hoặc khi được truyền vào hàm.

    let car1 = Car(price: 5000)
    let car2 = car1
    car2.price = 10000
    print(car1.price) // -> 10000.0
    print(car2.price) // -> 10000.0
    print(car1 === car2) // -> true

    Có thể hiểu đơn giản rằng, car1 sẽ tự gán chính bản thân nó cho car2 chứ không tạo ra các bản copy như struct, bởi vậy khi thay đổi thuộc tính của car2 thì car1 cũng bị thay đổi theo.

    Note: Có thể kiểm tra 2 đối tượng có cùng trỏ tới 1 instance hay không bằng toán từ ===

    Class có thể kế thừa, còn struct thì không

    Class hỗ trợ kế thừa, có thể tạo ra các class con kế thừa từ class cha để mang những thuộc tính, phương thức của class cha. Có thể thấy class hỗ trợ lập trình OOP tốt hơn struct.

    Các phương thức trong struct nếu muốn thay đổi thuọc tính thì phải thêm mutating

    Struct là kiểu value type. Mặc định thì các thuộc tính của 1 biến kiểu value type không thể bị sửa đổi trong các hàm của biến đó.
    Tuy nhiên, nếu bạn muốn thay đổi thuộc tính của 1 Struct bằng 1 phương thức bên trong nó, bạn phải khai báo mutating vào trước phương thức để làm:

    Nếu 1 hàm thay đổi thuộc tính mà không báo mutating thì trình biên dịch sẽ báo lỗi

    Class hỗ trợ hàm deinit:

    Class cung cấp hàm deinit. Hàm này được gọi trước khi 1 class được giải phóng khỏi memory.

    Ví dụ về sự khác biệt struct và class thường dùng

    Ví dụ như trong ví dụ sau đây đối với hàm repeating rất hay được sử dụng:

    class Dog {
        var name = ""
    }
    
    let dogs = [Dog](repeating: Dog(), count: 3)
    dogs[0].name = "Green"
    dogs[1].name = "Red"
    dogs[2].name = "Blue"
    
    print("\(dogs[0].name) \(dogs[1].name) \(dogs[2].name)") // Blue Blue Blue

    Bởi Dog là 1 class, nên 3 đối tượng Dog trong mảng dogs sẽ cùng trỏ tới 1 đối tượng giống nhau -> Vì vậy nên thay đổi giá trị của dogs[3] cũng sẽ thay đổi giá trị của các dog còn lại trong array.

    Thử sửa Dog thành kiểu struct và print ra kết quả.

    Khi nào nên sử dụng struct / class

    Recommend sử dụng struct bởi:

    • Struct nhanh hơn class bởi struct sử dụng method dispatch là static dispatch, class sử dụng dynamic dispatch. Ngoài ra, struct lưu dữ liệu trong stack, còn class sử dụng stack + heap -> Xử lí trong class sẽ lâu hơn.
    • Class là 1 reference type. Do đó, nếu không cẩn thận khi truyền biến sẽ dễ gây ra lỗi ngoài ý muốn ( Xem phần value type vs reference type ở trên). -> Sử dụng struct sẽ an toàn hơn.

    Nên sử dụng class khi:

    • Cần sử dụng kế thừa.
    • Cần sử dụng reference type

    Kết luận:

    Việc phân biệt được sự khác nhau giữa class và struct vô cùng quan trọng để có thể sử dụng cho đúng cách. Hi vọng qua bài viết, các bạn sẽ hiểu rõ hơn được về sự khác biệt giữa class và struct.

  • Mô phỏng AWS Lambda & API Gateway bằng Serverless Offline

    Mô phỏng AWS Lambda & API Gateway bằng Serverless Offline

    Khi phát triển ứng dùng bằng AWS Lambda không phải lúc nào chúng ta cũng có thể phát triển trực tiếp trên AWS được. Do đó việc giả lập môi trường AWS để có thể chạy được Lambda và API Gateway là cần thiết. Nó không chỉ giúp chúng ta có thể học mà còn giúp cho quá trình phát triển nhanh hơn. Trong bài viết này tôi sẽ hướng dẫn các bạn giả lập AWS Lambda và API Gateway bằng Serverless Offline

    Các công cụ cần thiết

    Trước tiên bạn cần cài đặt các tool cần thiết, bạn có thể tham khảo hướng dẫn cài đặt trong các bài viết sau:

    Bạn có thể dùng lệnh sau để cài serverless

    hieunv@HieuNV lambda % yarn global add serverless
    yarn global v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Installed "[email protected]" with binaries:
          - serverless
          - slss
          - sls
    ✨  Done in 14.23s.
    

    Tạo một project mới

    • Tạo project với yarn
    hieunv@HieuNV hieunv % mkdir lambda
    hieunv@HieuNV hieunv % cd lambda
    hieunv@HieuNV lambda % yarn init
    yarn init v1.22.0
    question name (lambda):
    question version (1.0.0):
    question description:
    question entry point (index.js):
    question repository url:
    question author:
    question license (MIT):
    question private:
    success Saved package.json
    ✨  Done in 3.53s.
    
    • Cài đặt serverless-offline
    hieunv@HieuNV lambda % yarn add serverless-offline -D
    
    • Cài đặt serverless-python-requirements để viết lambda handler bằng python
    hieunv@HieuNV lambda % yarn add serverless-python-requirements -D
    

    Cấu hình serverless.yml

    serverless.yml

    service: lambda
    
    frameworkVersion: '>=1.1.0 <2.0.0'
    
    provider:
      name: aws
      runtime: python3.7
    custom:
      serverless-offline:
        port: 4000
    plugins:
      - serverless-offline
      - serverless-python-requirements
    

    Cấu hình lambda handler đầu tiên trong serverless.yml

    Chúng ta tạo một Rest API sử dụng lambda bằng cách thêm đoạn sau vào file serverless.yml

    functions:
      test:
        handler: src.api.test.lambda_handler
        events:
          - http:
              method: get
              path: api/test
              cors: true
    

    Ở đây chúng ta tạo ra một Rest API với phướng thức GET và path /api/test. Các bạn nhìn thấy handler: src.api.test.lambda_handler đúng không. Đây là cấu hình hàm lamda sẽ được gọi bởi API Gateway

    Viết code cho lambda handler

    src/api/test.py

    import json
    
    
    def lambda_handler(event, context):
        headers = {"Access-Control-Allow-Origin": "*", "Accept": "application/json"}
        return {
            "statusCode": 200,
            "headers": headers,
            "body": json.dumps({"status": "success", "data": {}}),
        }
    

    Tạo script để run server

    Thêm đoạn sau vào package.json

        "scripts": {
            "start": "sls offline start"
        },
    

    Giờ thì chạy thôi nào các thanh niên

    hieunv@HieuNV lambda % yarn start
    yarn run v1.22.0
    $ sls offline start
    Serverless: Starting Offline: dev/us-east-1.
    
    Serverless: Routes for test:
    Serverless: GET /api/test
    Serverless: POST /{apiVersion}/functions/lambda-dev-test/invocations
    
    Serverless: Offline [HTTP] listening on http://localhost:4000
    Serverless: Enter "rp" to replay the last request
    

    Dùng Postman để call api vừa tạo nhé:

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp các bạn tiếp tục học và làm việc cùng với AWS Lambda và API Gateway trong các dự án của mình.

  • Android Animation (Part 3)

    Android Animation (Part 3)

    Xin chào các bạn, bài tiếp theo trong series về animation của mình đó là giới thiệu cho các bạn về AnimatedVectorDrawable.

    VectorDrawable

    Thông thường để có những hình ảnh tương tích với các màn hình khác nhau trong thiết bị Android thì bạn sẽ tạo ra các hình ảnh như hdpi, mdpi, xhdpi, xxhdpi, xxxhdpi.
    Để tối ưu cho phần này thì trong API 21, Android đã phát hành VectorDrawable giúp thay thế nhiều ảnh .png thành một đồ hoạ vector được tạo bằng xml.
    VectorDrawable bao gồm các điểm, đường thằng, đường cong, màu sắc…khi co giãn không làm ảnh hưởng tới chất lượng của ảnh. Đó thực sự là một điểm mạnh của VectorDrawable.

    Ví dụ VectorDrawable cho numeric_0 ở bài trước như dưới đây:

    <!-- drawable/numeric_0_vector.xml -->
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="24dp"
        android:height="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path
            android:fillColor="#000"
            android:pathData="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7A2,2 0 0,0 9,9V15A2,2 0 0,0 11,17H13A2,2 0 0,0 15,15V9A2,2 0 0,0 13,7H11M11,9H13V15H11V9Z" />
    </vector>

    cú pháp:
    <vector>: xác định một vector drawable cần vẽ.
    android:width & android:height: kích thước chiều rộng, chiều cao của hình dạng vector.
    android:viewportWidth & android:viewportHeight: khung cửa sổ để vẽ hình dạng vector.
    <path>: bên trong thẻ <vector>, xác định đường dẫn để vẽ.
    android:fillColor: màu bạn sử dụng
    android:pathData: các thuộc tính để vẽ và theo bộ quy tắc dưới đây:

    • M: di chuyển điểm vẽ đến tọa độ x, y (M x y).
    • L: vẽ từ điểm hiện tại đến điểm x, y (L x y).
    • H: vẽ đường ngang từ điểm hiện tại đến điểm có tọa độ x (H x).
    • V: vẽ đường thẳng đứng đến điểm có tọa độ y (V y).
    • C: vẽ đường cong cubic-bezier từ điểm hiện tại x0, y0 đến điểm x, y. Điểm đầu đường cong tiếp tuyến với đường thẳng x0,y0, x1, y1. Điểm thứ 2 của đường cong tiếp tuyến với tường x,y, x2, y2 C x1 y1, x2 y2, x, y.
    • S: vẽ đường cong trơn từ điểm hiện tại x0, y0 đến điểm x, y trong đó điểm đầu tiếp tuyến với đường x0,y0, x2, y2 S x2 y2, x y.
    • Q: vẽ đường cong cubic-bezier từ điểm hiện tại x0, y0 đến điểm x, y. điểm đầu đường cong tiếp tuyến với đường thẳng x0,y0, x1, y1 điểm thứ 2 của đường cong tiếp tuyến với tường x,y, x1, y1 C x1 y1, x y.
    • T: vẽ đường cong cubic-bezier, từ điểm hiện tại đến điểm x,y (T x y).
    • A: vẽ cung tròn.
    • Z: đóng đường vẽ.
    • Ngoài ra, chúng ta còn các cú pháp khác nữa. Bạn có thể tham khảo ở đây

    AnimatedVectorDrawable

    Để giới thiệu cho các bạn về AnimatedVectorDrawable thì tôi sẽ làm giống animation của bài trước, nhưng sẽ sử dụng AnimatedVectorDrawable để cho các bạn hình dung về nó dễ hơn.
    Như bài trước thì chúng ta cần 3 ảnh png: ic_numeric_0.png, ic_numeric_1.png, ic_numeric_2.png và các ảnh cho các màn hình khác nhau trong Android: hdpi, mhdpi, xhdpi, xxhdpi, xxxhdpi là có tới khoảng 15 hình ảnh tất cả phải không?.
    Ở bài này thì các bạn cần chuẩn bị 3 ảnh VectorDrawable cho mình nhé.
    Bạn có thể tìm kiếm ở đây .
    Mình sẽ sử dụng 3 ảnh VectorDrawable của bài trước.

    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19M11,7A2,2 0 0,0 9,9V15A2,2 0 0,0 11,17H13A2,2 0 0,0 15,15V9A2,2 0 0,0 13,7H11M11,9H13V15H11V9Z" />
    </vector>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M14,17H12V9H10V7H14M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
    </vector>
    <vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:height="24dp"
        android:width="24dp"
        android:viewportWidth="24"
        android:viewportHeight="24">
        <path android:fillColor="#000" android:pathData="M15,11C15,12.11 14.1,13 13,13H11V15H15V17H9V13C9,11.89 9.9,11 11,11H13V9H9V7H13A2,2 0 0,1 15,9M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
    </vector>

    Chú ý: AnimatedVectorDrawable yêu cầu bạn sử dụng các hình ảnh tương thích với nhau (bạn hiểu đơn giản là android:pathData của các hình ảnh phải có cùng các lệnh, theo cùng một thứ tự và có cùng số lượng tham số cho mỗi lệnh…)
    Các ảnh bên trên sẽ không tương thích nên bài viết này mình sẽ sử dụng ShapeShifter là một ứng dụng web được tạo bởi Alex Lockwood, giúp cho các ảnh .svg tương thích với nhau.
    Sau khi mình sử dụng ShapeShifter thì sẽ được kết qủa như dưới đây animated_vector.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:aapt="http://schemas.android.com/aapt">
        <aapt:attr name="android:drawable">
            <vector
                android:name="vector"
                android:width="96dp"
                android:height="96dp"
                android:viewportWidth="24"
                android:viewportHeight="24">
                <path
                    android:name="path"
                    android:fillColor="#000"
                    android:pathData="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z" />
            </vector>
        </aapt:attr>
        <target android:name="path">
            <aapt:attr name="android:animation">
                <set>
                    <objectAnimator
                        android:duration="1000"
                        android:interpolator="@android:interpolator/fast_out_slow_in"
                        android:propertyName="pathData"
                        android:valueFrom="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z"
                        android:valueTo="M 19 3 C 19.53 3 20.039 3.211 20.414 3.586 C 20.789 3.961 21 4.47 21 5 L 21 19 C 21 19.53 20.789 20.039 20.414 20.414 C 20.039 20.789 19.53 21 19 21 L 5 21 C 4.47 21 3.961 20.789 3.586 20.414 C 3.211 20.039 3 19.53 3 19 L 3 5 C 3 4.47 3.211 3.961 3.586 3.586 C 3.961 3.211 4.47 3 5 3 L 19 3 M 11 7 C 10.47 7 9.961 7.211 9.586 7.586 C 9.211 7.961 9 8.47 9 9 L 9 15 C 9 15.53 9.211 16.039 9.586 16.414 C 9.961 16.789 10.47 17 11 17 L 13 17 C 13.53 17 14.039 16.789 14.414 16.414 C 14.789 16.039 15 15.53 15 15 L 15 9 C 15 8.47 14.789 7.961 14.414 7.586 C 14.039 7.211 13.53 7 13 7 L 11 7 M 11 9 L 13 9 L 13 15 L 11 15 L 11 9 Z"
                        android:valueType="pathType" />
                    <objectAnimator
                        android:duration="1000"
                        android:interpolator="@android:interpolator/fast_out_slow_in"
                        android:propertyName="pathData"
                        android:startOffset="1000"
                        android:valueFrom="M 14 17 L 12 17 L 12 9 L 10 9 L 10 7 L 14 7 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
                        android:valueTo="M 14 17 L 12 17 L 12 9 L 10 9 L 10 7 L 14 7 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
                        android:valueType="pathType" />
                    <objectAnimator
                        android:duration="1000"
                        android:interpolator="@android:interpolator/fast_out_slow_in"
                        android:propertyName="pathData"
                        android:startOffset="2000"
                        android:valueFrom="M 15 11 C 15 12.11 14.1 13 13 13 L 11 13 L 11 15 L 15 15 L 15 17 L 9 17 L 9 13 C 9 11.89 9.9 11 11 11 L 13 11 L 13 9 L 9 9 L 9 7 L 13 7 C 13.53 7 14.039 7.211 14.414 7.586 C 14.789 7.961 15 8.47 15 9 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
                        android:valueTo="M 15 11 C 15 12.11 14.1 13 13 13 L 11 13 L 11 15 L 15 15 L 15 17 L 9 17 L 9 13 C 9 11.89 9.9 11 11 11 L 13 11 L 13 9 L 9 9 L 9 7 L 13 7 C 13.53 7 14.039 7.211 14.414 7.586 C 14.789 7.961 15 8.47 15 9 M 19 3 L 5 3 C 4.47 3 3.961 3.211 3.586 3.586 C 3.211 3.961 3 4.47 3 5 L 3 19 C 3 19.53 3.211 20.039 3.586 20.414 C 3.961 20.789 4.47 21 5 21 L 19 21 C 19.53 21 20.039 20.789 20.414 20.414 C 20.789 20.039 21 19.53 21 19 L 21 5 C 21 4.47 20.789 3.961 20.414 3.586 C 20.039 3.211 19.53 3 19 3 Z"
                        android:valueType="pathType" />
                </set>
            </aapt:attr>
        </target>
    </animated-vector>

    Bài viết tiếp theo mình sẽ giới thiệu kỹ hơn đến các bạn về ShapeShifter.
    Còn giờ thì hãy chạy để xem kết quả của bạn vừa làm với những dòng code dưới đây:

    val animatedVectorDrawableCompat =
                AnimatedVectorDrawableCompat.create(this, R.drawable.animated_vector)
            val imageView = (findViewById<ImageView>(R.id.numeric_image)).apply {
                setImageDrawable(animatedVectorDrawableCompat)
            }
            animatedVectorDrawableCompat?.registerAnimationCallback(object :
                Animatable2Compat.AnimationCallback() {
                override fun onAnimationEnd(drawable: Drawable?) {
                    imageView.post { animatedVectorDrawableCompat.start() }
                }
            })
            animatedVectorDrawableCompat?.start()
    AnimatedVectorDrawable

    Mình rất mong được các bạn đón đọc và để lại lời bình luận để mình cải thiện hơn nữa <3

    Link tham khảo:

    • https://developer.android.com/reference/android/graphics/drawable/VectorDrawable
    • https://developer.android.com/guide/topics/graphics/vector-drawable-resources
  • Video codec for Developer – Getting start

    Video codec for Developer – Getting start

    Chắc hẳn ace đều đã, đang và có thể sẽ làm việc liên quan tới xử lí video (video playing, video streaming, video processing, …). Đây là một thế giới với kho kiến thức khổng lồ, hàng trăm, hàng ngàn paper phải đọc. Mình quyết định sẽ viết một series liên quan tới video codec dưới góc độ làm việc của một developer chứ không phải là R&D để tránh lan man.

    Thông qua lượt bài này, mình hi vọng mọi người có thể tiếp cận nhanh và tối giản nhất có thể, để không cần phải đọc và hiểu hàng tá các thuật ngữ, các paper mà có thể khiến chúng ta nản ngay từ vòng gửi xe. Mình sẽ không đi quá sâu vào bất kì thành phần nào mà chỉ đưa ra những keyword cơ bản nhất, cũng như cố gắng giải thích dễ hiểu nhất nhằm giúp người đọc nắm bắt nhanh nhất.

    Video codec là gì?

    Nói một cách đơn giản thì đây là một (hoặc tập hợp) phương pháp mã hoá video. Mỗi video codec có thể được implement dựa vào phần cứng (hardware coding) hoặc phần mềm (software coding). Tất nhiên nếu sử dụng phần cứng thì sẽ luôn có sự tối ưu hơn với performance tốt hơn rất nhiều, nhưng một chipset sẽ không thể implement quá nhiều các loại thuật toán, vì thế nó vẫn luôn tồn tại song hành với software coding. Tuỳ vào cách lập trình của các lập trình viên cũng như sự hỗ trợ từ framework mà hardware coding có thể được ưu tiên hoặc không.

    Phần lớn các chuẩn mã hoá đều là lossy compression (nén có hư tổn dữ liệu) nhằm tối ưu dung lượng lưu trữ cũng như tối ưu truyền tải dữ liệu trên internet. Người nén có thể lựa chọn cân bằng chất lượng hình ảnh với dung lượng từ video gốc sao cho phù hợp với nhu cầu của họ cũng như người sử dụng.

    Có rất nhiều các loại codec cũng như các loại chuẩn (standard) được phát triển từ xưa tới nay, đến từ nhiều hãng khác nhau (như MPEG, Google, …), tuy nhiên mình sẽ chỉ focus vào nhóm chuẩn của MPEG (Moving Picture Experts Group) vì đây là các chuẩn thông dụng nhất, và cũng được hỗ trợ nhiều nhất ở thời điểm hiện tại.

    Sơ lược về cách hoạt động của quá trình nén

    Video codec làm gì mà thần thánh tới mức có thể giảm dung lượng video ghê gớm như vậy? Sau đây mình sẽ giải thích qua cơ chế xử lí của nó để các bạn nắm bắt được nhé.

    Một video gốc (raw video) bao gồm rất nhiều các frames (raw frame) nối tiếp với nhau. Mỗi raw frame sẽ chứa thông tin chính xác của từng điểm ảnh, vì thế dung lượng của video sẽ bằng dung lượng của frame nhân với số lượng frame. Thật là kinh hoàng nếu nghĩ tới 1 video 4K@60fps dài khoảng 1 tiếng đúng ko? Các đĩa Bluray ngoài rạp coi film phần lớn đều sử dụng các video gốc như vậy (thậm chí là nhiều lớp) để tăng độ chi tiết cũng như trải nghiệm của người xem ở rạp. Đó là lí do vì sao coi film chiếu rạp luôn có cảm giác "thật" hơn là coi film nén trên mạng, mặc dù độ phân giải là như nhau đó.

    Sẽ thật lãng phí nếu có một điểm ảnh không thay đổi trong suốt quá trình ghi lại video. Nếu điểm ảnh đó đã không thay đổi, thì chi bằng ta … khỏi lưu nó lại làm gì, phải không? Okay, đây là nguồn gốc quá trình nén của bất kì loại codec nào có hiện nay, chỉ có điều các kiểu nén có sự khác biệt về thuật toán mà thôi.

    +-------+     +-------+
    |   I   |     |   P   |
    +-------+     +-------+      +-------+
    |xxxxxxx|     | o     |      |xoxxxxx|
    |xxxxxxx|  +  |    o  |  ->  |xxxxoxx|
    |xxxxxxx|     |   o   |      |xxxoxxx|
    +-------+     +-------+      +-------+
    

    Ta tạm hiểu rằng mỗi video khi bắt đầu sẽ cần có 1 frame gốc để dựng hình, cái này gọi là intraframe (viết tắt là I-frame, hoặc còn được biết với các tên là keyframe). Đây là frame gốc, lưu trữ toàn bộ các điểm ảnh ban đầu để khi giải mã có thể dựng được frame đầu tiên (tất nhiên frame này cũng đã được nén, tương tự như nén ảnh). Sau intraframe là các interframe, mà nó không lưu trữ đủ thông tin các điểm ảnh, chỉ lưu trữ thông tin SỰ KHÁC NHAU giữa 2 frame. Có 2 loại interframe là P-frame (predictive frame) và B-frame (bi-directional predictive frame). P-frame được giải mã dựa trên I-frame hoặc P-frame ngay phía trước nó. B-frame cũng tương tự như P-frame, nhưng nó có thể giải mã dựa trên thông tin của cả frame phía sau nó nữa.

    Trong một video sẽ có rất nhiều intraframe, mà tần suất xuất hiện của nó được qui định bằng GOP (hoặc GOV). Mục đích của nó là để giải quyết bài toán seeking, cũng như hạn chế việc loss thông tin trong quá trình giải mã video thì cần sync lại để tránh video bị lỗi quá nhiều. GOP (GOV) là viết tắt của cụm từ Group of Picture (hoặc Group of Video), mô tả số lượng I/P/B frames từ keyframe này tới keyframe tiếp theo. Trong một số hệ thống encoding sẽ không xuất hiện tham số GOP này mà sử dụng tham số I-frame interval. Con số này mô tả rằng sau bao nhiêu giây sẽ xuất hiện 1 I-frame

    +-------------------------------------------------------------------------------+
    | I | P | P | P | P | P | P | P | P | P | I | P | P | P | P | P | P | P | P | P |
    +-------------------------------------------------------------------------------+
    |                GOP = 10               |                GOP = 10               |
    +-------------------------------------------------------------------------------+
    |                                   20 frames                                   |
    +-------------------------------------------------------------------------------+
    

    Nếu ta cho rằng video trong sơ đồ phía trên có framerate (tốc độ khung hình) là 20fps, vậy đồng nghĩa với việc I-frame interval là 0.5. Quá đơn giản phải không nào 😀

    Chất lượng của việc nén sẽ được qui định bằng con số bitrate, với đơn vị thường thấy là kbps (kilobits per sec). Hiểu một cách nôm na thì đây chính là kích thước thực của video trong mỗi giây. Con số này có thể là không đổi cho toàn bộ video (CBR – Constant Bitrate), hoặc cũng có thể thay đổi tuỳ vào thời điểm của video (VBR – Variable Bitrate), được qui định tại thời điểm encode. Giả dụ ta có 1 bài toán đơn giản như sau:

    1 video được nén CBR, độ dài là 30s với bitrate = 5120kbps
    =&gt; dung lượng của video = 5120 / 8 * 30 = 19200 KB ~ 18.75MB
    

    Như các bạn có thể thấy, dung lượng của video không còn quan tâm tới resolution (độ phân giải), mà dung lượng được quyết định bởi bitrate. Ồ, vậy thì độ phân giải còn có vai trò gì nữa? Tất nhiên là nó vẫn có rỗi. Với kiến thức thông thường, ta biết rằng độ phân giải sẽ quyết định mức độ chi tiết của ảnh. Độ chi tiết càng cao thì lượng thông tin càng lớn. Vì vậy nếu ta giữ nguyên bitrate để nén video với lượng thông tin lớn, thì hiển nhiên chất lượng của nó sẽ kém đi. Đây chính là lí do vì sao đôi khi ta gặp những video 4K nhưng lại có chất lượng kém hơn cả video 2K hay 1080p, đơn giản vì nó có bitrate thấp, không tương xứng với chất lượng hình ảnh.

    Như ví dụ dưới đây, mình sẽ minh hoạ 2 trường hợp phổ biến mà ta thường gặp. Cho rằng dữ liệu gốc là không đổi (4K lossless downscale xuống 1K lossless), các bạn thử tự hình dung xem kết quả nào cho chất lượng tốt hơn nhé?

    * TH1: nén video 4K với 300 units = bitrate 5120kbps?
    * TH2: nén video 1K với 100 units = bitrate 5120kbps?
    

    Trên đây mình đã mô tả một cách cơ bản nhất về quá trình mã hoá video. Các kiến thức trên là cơ bản nhất và nó gần như đúng với mọi loại thuật toán nén, mọi loại chuẩn nén. Các thuật toán đời mới hiện nay chỉ có sự khác biệt về tối ưu hoá chất lượng, dung lượng cũng như performance mà thôi.

    Hẹn gặp lại các bạn ở bài viết sau. Nếu có phần nào không đúng, nhờ các bạn góp ý để chúng ta có nhiều hiểu biết hơn nhé.

  • [React] Tôi đã tạo thư viện React Component như thế nào

    [React] Tôi đã tạo thư viện React Component như thế nào

    Trong bài viết này tôi sẽ hướng dẫn các bạn tạo ra một thư viện component của riêng mình. Bạn cũng có thể public thư viện nếu thích. Nhiều bạn sẽ tự hỏi rằng có nhiều thư viện lắm rồi thì viết thêm làm chi. Mục đích mình viết bài này để có thể tự tạo ra một package gồm những common component có thể sử dụng lại được. Kết hợp với việc dùng mono repository gồm nhiều packages trong yarn workspaces, các bạn có thể tách code của mình thành các package cho dễ quản lý. Chúng ta cùng bắt đầu nhé.

    Thêm các dependencies cần thiết

    • react & react-dom React component mà nên cần 2 thư viện này là đương nhiên rồi.
    hieunv@HieuNV uikit % yarn add react react-dom
    yarn add v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Saved lockfile.
    success Saved 3 new dependencies.
    info Direct dependencies
    ├─ [email protected]
    └─ [email protected]
    info All dependencies
    ├─ [email protected]
    ├─ [email protected]
    └─ [email protected]
    ✨  Done in 7.91s.
    
    yarn add v1.22.0
    info No lockfile found.
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    ✨  Done in 15.05s.
    
    hieunv@HieuNV uikit % yarn add react-scripts -D
    yarn add v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    ✨  Done in 41.42s.
    

    Chúng ta sẽ sử dụng webpack config đã được config trong react-scripts

    Cấu hình styleguidist

    styleguidist.config.js

    module.exports = {
      propsParser: require('react-docgen').parse,
      webpackConfig: require('react-scripts/config/webpack.config')
    };
    

    Thêm script run styleguidist

    package.json

    {
      "name": "uikit",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "scripts": {
        "dev": "npx styleguidist server"
      },
      "devDependencies": {
        "react-scripts": "^3.4.0",
        "react-styleguidist": "^10.6.2"
      },
      "dependencies": {
        "react": "^16.13.0",
        "react-dom": "^16.13.0"
      }
    }
    

    Khởi động dev server thôi nào

    hieunv@HieuNV uikit % yarn dev
    yarn run v1.22.0
    $ npx styleguidist server
    Loading webpack config from:
    /Users/hieunv/Projects/hieunv/uikit/node_modules/react-scripts/config/webpack.config.js
    
    ℹ 「wds」: Project is running at http://localhost:6060/
    ℹ 「wds」: webpack output is served from undefined
    ℹ 「wds」: Content not from webpack is served from /Users/hieunv/Projects/hieunv/uikit
    You can now view your style guide in the browser:
    
      Local:            http://localhost:6060/
      On your network:  http://192.168.1.11:6060/
    

    Sau khi khởi động server xong các bạn truy cập và link http://192.168.1.11:6060/

    Do chưa có component nào được định nghĩa nên các bạn sẽ nhận được thông báo như trên.

    Bắt đầu viết component đầu tiên thôi nào

    styleguidist sẽ tự động đọc các components trong thư mục src/components/*README.md trong thư mục component tương ứng để generate ra document sử dụng react-docgen trên styleguidist server

    src/component/Button/Button.jsx

    module.exports = {
      propsParser: require('react-docgen').parse,
      webpackConfig: require('react-scripts/config/webpack.config')
    };
    

    src/component/Button/README.md

    <Button>Test Button</Button>
    

    Chúng ta truy cập lại vào http://192.168.1.11:6060/ sẽ thấy component được sử dụng để test và có cả document nữa luôn.

    Styleguidist server với react-docgen

    Export component ra bên ngoài để có thể sử dụng ở package khác

    src/index.js

    export * from './components/Button/Button';
    
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết sẽ giúp ích cho các dự án của bạn.

  • [React] Loading indicator with React Context API & Hooks

    [React] Loading indicator with React Context API & Hooks

    Loading indicator có lẽ là component huyền thoại mà dự án nào cũng cần đến. Hôm nay mình sẽ hướng dẫn các bạn viết nó thành dạng global component để có thẻ được gọi tại bất kỳ đâu trong ứng dụng của bạn. Chúng ta cùng bắt đầu nhé.

    Để hiểu thêm về React Context API & Hooks các bạn tham khảo thêm tại đây:

    Chúng ta bắt đầu bằng việc tạo một dự án mới sử dụng React nhé. Trong bài viết này mình sẽ sử dụng create-react-app cùng với yarn để tạo project mới. Các bạn xem hướng dẫn cài yarn tại đây nhé.

    Tạo một project mới

    • Cài create-react-app package:
    hieunv@HieuNV ~ % yarn global add create-react-app
    yarn global v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Installed "[email protected]" with binaries:
          - create-react-app
    ✨  Done in 14.08s.
    
    • Sau khi cài xong thì các bạn tạo project mới như sau:
    yarn create react-app loading
    
    Cấu trúc project được tạo bằng create-react-app

    Tạo React context mới để lưu trạng thái của Loading component

    context/loading.js

    import React, { useContext } from 'react';
    
    export const LoadingContext = React.createContext();
    
    export const useLoading = () => useContext(LoadingContext);
    

    Tạo LoadingProvider để lưu trạng thái loading như là một global state.

    providers/LoadingProvider.jsx

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    import { LoadingContext } from '../../contexts/loading';
    
    export function LoadingProvider(props) {
      const [loading, setLoading] = useState(false);
    
      return (
        <LoadingContext.Provider
          value={{
            loading: loading,
            show: () => setLoading(true),
            hide: () => setLoading(false)
          }}>
          {props.children}
        </LoadingContext.Provider>
      );
    }
    
    LoadingProvider.propTypes = {
      children: PropTypes.node
    };
    

    Như các bạn nhìn thấy trong đoạn mã trên chúng ta đã sử dụng LoadingContext truyền trang thái loading và các hàm showhide xuống các component con thông qua React Context.

    Tại App component bạn đặt LoadingProvider là component cao nhất để tất các các component con bên trong có thể gọi các hàm showhide khi cần thiết.

    App.js

    import React from 'react';
    import { LoadingProvider } from './providers/LoadingProvider';
    import { useLoading } from './context/loading.js';
    import logo from './logo.svg';
    
    import './App.css';
    
    function App() {
      const { show, hide } = useLoading();
      return (
        <LoadingProvider>
          <div className="App">
            <header className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <p>
                Edit <code>src/App.js</code> and save to reload.
              </p>
              <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer">
                Learn React
              </a>
            </header>
          </div>
        </LoadingProvider>
      );
    }
    
    export default App;
    

    Tại component bạn muốn gọi show hay hide loading thì chỉ cần thực hiện như sau:

    • import hàm useLoading() được khai báo trong context/loading.js
    import { useLoading } from './context/loading.js';
    
    • Trong component bạn sử dụng hook useLoading để lấy các hàm showhide:
    const { show, hide } = useLoading();
    

    Giờ chúng ta sửa lại một chút ở LoadingProvider để có thể show loading indicator

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    import { LoadingContext } from '../../contexts/loading';
    
    function Loading() {
      return <div>Loading...</div>;
    }
    
    export function LoadingProvider(props) {
      const [loading, setLoading] = useState(false);
    
      return (
        <LoadingContext.Provider
          value={{
            loading: loading,
            show: () => setLoading(true),
            hide: () => setLoading(false)
          }}>
          <>
            {loading && <Loading />}
            {props.children}
          </>
        </LoadingContext.Provider>
      );
    }
    
    LoadingProvider.propTypes = {
      children: PropTypes.node
    };
    

    Các bạn để ý component Loading nhé. Chúng ta chỉ cần thêm một chút kỹ thuật css để cho component này đề lên trên tất cả các component khác bằng z-index đã có được loading indicator có thể được gọi ở bất kỳ đâu rồi.

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp ích cho dự án của các bạn.