Month: March 2020

  • SQLite with FMDB in Swift (P1)

    SQLite with FMDB in Swift (P1)

    Đối với việc làm ứng dụng phần mềm, đa số đều phải sử dụng đến database. Core Data của Apple cung cấp là 1 trong những sự lựa chọn phổ biến đối với ứng dụng trên iphone.
    Theo í kiến cá nhân thì Core Data thường được dùng cho các database được tạo ra trong quá trình sử dụng, còn trong trường hợp nếu bạn có 1 database có sẵn để đưa vào app thì sao? Khi đó thì cần đưa 1 database đã được nhập sẵn vào app. SQlite là 1 lựa chọn tốt Ở bài viết này, mình sẽ giới thiệu cách sử dụng SQLite trong swift bằng thư viện FMDB.

    Nội dung bài viết

    • Giới thiệu về FMDB
    • Tạo đường dẫn cho 1 database
    • Khởi tạo 1 database
    • Tạo kết nối đến database
    • Thực hiện truy vấn

    FMDB Library:

    • FMDB là 1 thư viện hỗ trợ truy vấn Sqlite và quản lí dữ liệu hiệu quả.
    • Giúp bạn xử lí việc open connection/ close connection đến DB.
    • Có thể dùng cho cả swift hoặc obj-C.
    • Dễ dàng và nhanh chóng để tích hợp vào 1 project.
    • Cách sử dụng tương đối đơn giản, cho phép người dùng tự viết câu truy vấn theo ý muốn.

    Bài viết này tập trung vào việc sử dụng SQlite bằng thư viện FMDB, không hướng dẫn về phần cài đặt FMDB. Nếu bạn chưa biết về cách cài đặt thư viện, hãy xem thêm tại:

    https://guides.cocoapods.org/using/using-cocoapods.html

    Trong bài viết này, mình sẽ lấy 1 ví dụ về việc khởi tạo và viết các câu truy vấn đến 1 DB chứa các quyển sách.
    Khởi tạo struct Book:

    struct Book {
        let id: Int
        let name: String
        let author: String
        let price: Double
        
        func toString() {
            print("Book's info: id: \(id), name: \(name), author: \(author), price: \(price)")
        }
    }
    

    Tạo 1 class DatabaseManager để thực hiện việc truy vấn DB.
    Khởi tạo 1 biến database kiểu FMDatabase – Sẽ dùng để truy cập và thực hiện truy vấn đến DB.

    import Foundation
    import FMDB
    
    class DatabaseManager {
        static let shared = DatabaseManager()
        var database: FMDatabase!
    }

    Tạo đường dẫn để lưu DB:

    Nếu chưa có 1 DB có sẵn, thì khi tạo 1 DB cần phải có đường dẫn để lưu DB. Khởi tạo 1 đường dẫn cho 1 database có tên bookDB như sau:

    func getDatabasePath() -> String {
       let directoryPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
       let path = directoryPath.first!.appendingPathComponent("musicDB.db")
       print(path)
       return path.description
    }

    Sau khi print được ra đường dẫn, để xem database vừa tạo: Vào Finder, sử dụng tổ hợp phím Cmd + Shift + G, post đường dẫn vừa lấy được vào, Finder sẽ đưa đến database vừa tạo.

    Trong trường hợp đã có 1 DB bookDB có sẵn và được kéo vào trong app, thì lấy đường dẫn như sau:

    func getPathOfExistDB() -> String? {
       if let path = Bundle.main.path(forResource: "bookDB", ofType: ".db") {
           return path
       } 
       return nil
    }

    Khởi tạo 1 database

    func createDatabase() {
        if database == nil {
            if !FileManager.default.fileExists(atPath: getDatabasePath()) {
                database = FMDatabase(path: getDatabasePath())
            }
        }
    }

    Nếu 1 DB chưa tồn tại, thì khởi tạo 1 DB mới tại 1 đường dẫn đơn giản bằng cách khởi tạo 1 object FMDatabase tại 1 đường dẫn.

    Note: Việc check xem đã tồn tại 1 DB tại 1 path cụ thể trước khi khởi tạo rất quan trọng, bởi nếu ta path đó đã có sẵn 1 DB rồi thì DB đó sẽ bị hủy đi để khởi tạo 1 DB mới.

    Tạo kết nối đến Database:

    Note: Khi muốn truy suất đến DB thì phải mở 1 liên kết với DB, khi đã hoàn thành việc truy suất thì phải đóng liên kết lại.

    • Tạo liên kết tới DB:
    database.open()
    • Đóng liên kết tới DB:
    database.close()

    2 hàm trên đều trả về giá trị Bool để biết việc mở/đóng liên kết có thành công hay không.
    Ta có thể kết hợp việc tạo DB cùng với việc mở/đóng liên kết thành 1 hàm để tiện sử dụng:

    func openDatabaseConnectionAtPath(path: String) -> Bool {
        if database == nil {
            if !FileManager.default.fileExists(atPath: path) { // don’t want to create the database file again and destroy the original database.
                database = FMDatabase(path: path)
            }
        }
            
        if database != nil {
            if database.open() {
                print("Open Success")
                return true
            }
        }
        print("Open failed")
        return false
    }

    Tạo 1 bảng trong DB:

    Câu truy vấn tạo bảng:

    Viết câu truy vấn tạo bảng trong FMDB như sau:

    let query = "create table Book (id integer primary key autoincrement not null, name text not null, author text not null, price float not null default 0)"

    Để chạy câu truy vấn:

    database.executeUpdate(query, values: nil)

    Câu lệnh executeUpdate(…) dùng để thực hiện những câu truy vấn tạo ra sự thay đổi đến DB.

    • Thuộc tính values của hàm executeUpdate sẽ được nói ở phần sau, ở đây tạm thời để nil.
    • Hàm thực hiện câu truy vấn tạo bảng:
    func createBookTable() {
        if openDatabaseConnectionAtPath(path: getDatabasePath()) {
            let query = "create table Book id integer primary key autoincrement not null, name text not null, author text not null, price float not null default 0"
            do {
                try database.executeUpdate(query, values: nil)
            } catch let err {
                print("Execute query failed. error: \(err.localizedDescription)")
            }
        }
        database.close()
    }

    Hàm createBookTable ở trên sẽ tạo ra 1 DB nếu DB chưa tồn tại, còn nếu DB đã tồn tại rồi thì sẽ tạo liên kết, thực hiện truy vấn rồi đóng liên kết.
    Thực hiện câu truy vấn createBookTable:

    Kiểm tra kết quả:

    Ở phần tiếp theo, sẽ nói về các câu truy vấn insert, delete,…

  • iOS/Auto Layout – Phần 4: Làm sao để viết constraint dễ dàng hơn? Giới thiệu Snapkit

    iOS/Auto Layout – Phần 4: Làm sao để viết constraint dễ dàng hơn? Giới thiệu Snapkit

    Lời mở đầu

    Ở phần 3 chúng ta đã được làm quen với việc tạo constraints bằng code. Vậy nên bài viết này mình sẽ chia sẻ với các bạn về việc làm sao để tạo constraints bằng code dễ dàng hơn, tiết kiệm thời gian hơn. Và làm thế nào để debug UI.



    Sử dụng extension

    Việc sử dụng extension để mở rộng các class giúp chúng ta có thể tạo constraint dễ dàng và tiết kiệm nhiều thời gian. Việc của các bạn là nghĩ và tạo ra các func có thể hay được sử dụng.

    1. UIView

    Ví dụ chung ta có thể tạo ra một func trong extension UIView giúp chúng ta tạo các constraints liên kết với SuperView như sau:

    extension UIView {
    
        /// Returns a collection of constraints to anchor the bounds of the current view to the given view.
        ///
        /// - Parameter view: The view to anchor to.
        /// - Returns: The layout constraints needed for this constraint.
        func constraintsForAnchoringTo(boundsOf view: UIView) -> [NSLayoutConstraint] {
            return [
                topAnchor.constraint(equalTo: view.topAnchor),
                leadingAnchor.constraint(equalTo: view.leadingAnchor),
                view.bottomAnchor.constraint(equalTo: bottomAnchor),
                view.trailingAnchor.constraint(equalTo: trailingAnchor)
            ]
        }
    }

    Từ đây các bạn có thể tha hồ nghĩ thêm các func để giúp cho việc tạo constraints của mình trở nên đơn giản và tiết kiệm thời gian hơn nhiều so với việc hì hục ngồi code.

    1.2 Xử lí ưu tiên

    Khi bạn phải đặt độ ưu tiên cho các constraints của mình để tránh phá vỡ các constraint khác. Bạn có thể sẽ cần đến extension này:

    extension NSLayoutConstraint {
        
        /// Returns the constraint sender with the passed priority.
        ///
        /// - Parameter priority: The priority to be set.
        /// - Returns: The sended constraint adjusted with the new priority.
        func usingPriority(_ priority: UILayoutPriority) -> NSLayoutConstraint {
            self.priority = priority
            return self
        }
        
    }
    
    extension UILayoutPriority {
        
        /// Creates a priority which is almost required, but not 100%.
        static var almostRequired: UILayoutPriority {
            return UILayoutPriority(rawValue: 999)
        }
        
        /// Creates a priority which is not required at all.
        static var notRequired: UILayoutPriority {
            return UILayoutPriority(rawValue: 0)
        }
    }

    Độ ưu tiên của constraint là phần khá là hay ho, nên mình sẽ dành một mục ở bài viết tiếp theo.

    1.3 Tạo Auto layout property wrapper

    Khi auto layout bằng code, để tránh việc phải viết đi viết lại nhiều lần dòng code dưới đây:
    translatesAutoresizingMaskIntoConstraints = false

    Chúng ta sẽ tạo ra Property Wrapper như dưới đây:

    @propertyWrapper
    public struct UsesAutoLayout<T: UIView> {
        public var wrappedValue: T {
            didSet {
                wrappedValue.translatesAutoresizingMaskIntoConstraints = false
            }
        }
    
        public init(wrappedValue: T) {
            self.wrappedValue = wrappedValue
            wrappedValue.translatesAutoresizingMaskIntoConstraints = false
        }
    }
    
    final class MyViewController {
        @UsesAutoLayout
        var label = UILabel()
    }
    

    Lưu ý: Nó không sử dụng được cho biến Local

    Giới thiệu về Snapkit

    Nếu như các bạn lười nghĩ và viết các extension để hỗ trợ cho việc tạo constraint bằng code. Thì chúng ta lại có một giải pháp khác là dùng của thằng khác :D. Của ăn sẵn nhiều khi cũng tốt mà. Tiện đây mình xin giới thiệu với các bạn về thư viện Snapkit một thư việt rất mạnh về Auto layout bằng code. Giúp chúng ta xử lí Autolayout một cách đơn giản, gọn gàng và tiết kiệm khá nhiều thời gian.

    Ưu điểm
    – Code nhìn gọn gàng, dễ hiểu hơn
    – Tiết kiệm thời gian khi code

    Nhược điểm
    – Sẽ có những thành viên không sử dụng snapkit mà dùng API default của apple dẫn đến thời gian đầu làm việc gặp khó khăn.

    Cài đặt

    CocoaPods, Carthage hoặc ném nó vào project của bạn.
    Tham khảo link này nhé.

    Cách sử dụng

    1.Tạo constraint

    func makeConstraintBySnapkit1() {
            let iView = UIView()
            let oView = UIView()
            iView.backgroundColor = .red
            oView.backgroundColor = .blue
    
            view.addSubview(oView)
            oView.addSubview(iView)
    
            oView.snp.makeConstraints { (make) in
                make.width.height.equalTo(200)
                make.center.equalToSuperview()
            }
            iView.snp.makeConstraints { (make) in
                make.top.leading.equalToSuperview().offset(20)
                make.bottom.trailing.equalToSuperview().offset(-20)
            }
        }

    Để tạo được constraint có kết quả như hình này bằng code sử dụng API mặc định của apple chúng ta sẽ phải viết khá nhiều dòng code.

    2. Giữ reference của constraint và thay đổi nó giá trị của nó khi cần.

    var myConstraint: Constraint?
    iView.snp.makeConstraints { (make) in
                self.myConstraint = make.top.leading.equalToSuperview().offset(20).constraint
                make.bottom.trailing.equalToSuperview().offset(-20)
            }
    myConstraint?.update(offset: 40)

    3. Update constraint

    Update constraint giúp chúng ta update tùy ý bất kể 1 constraint nào đó của View.
    Để update constraint ta chỉ cần viết như sau:

    iView.snp.updateConstraints { (edit) in
        edit.top.leading.equalToSuperview().offset(0)
    }

    Kết quả thu được sẽ là:

    4. Remake constraint

    Khác với updateContraint, remake constraint sẽ remove toàn bộ constraints cũ đi và bạn sẽ tạo lại constraint từ đầu như hàm makeConstraint.
    Câu lênh của nó sẽ như sau:

            iView.snp.remakeConstraints { (remake) in
                remake.top.leading.equalToSuperview().offset(20)
                remake.bottom.trailing.equalToSuperview()
            }

    Kết quả thu được

    Để có thể thành thạo sử dụng Auto Layout và Snapkit chúng ta chỉ có 1 cách đó là làm nhiều, nhiều nữa và nhiều mãi thế thôi =)) Cố gắng lên anh em :))

    Tổng kết

    Mình hi vọng bài viết này sẽ giúp các bạn phần nào sử dụng Auto Layout dễ dàng và tiết kiệm thời gian hơn.



  • [React] Tôi đã làm toast component như thế nào?

    [React] Tôi đã làm toast component như thế nào?

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Với bất kỳ ứng dụng nào việc hiển thị thông báo lỗi là không thể thiếu được. Các bạn có thể có rất nhiều cách làm để hiển thị được các thông báo lỗi này. Trong bài viết này tôi xin chia sẻ cách làm của tôi để các bạn cùng tham khảo nhé.

    Mục đích của tôi khi viết component này phải đảm bảo nó dễ dùng như hàm window.alert() vậy. Trong bài viết này tôi sẽ sử dụng Material UI để hiển thị thông báo lỗi.

    Toast Context

    Vì chúng ta cần hàm hiển thị thông báo lỗi có thể sử dụng như window.alert() nên chúng ta cần khai báo một nơi lưu trữ để từ đó chúng ta có thể gọi được ở bất kỳ đâu trong ứng dụng. Có nhiều bạn sẽ nghĩ tới Redux nhưng trong bài viết này tôi sẽ sử dụng React Context API để làm việc đó. Chúng ta bắt đầu bằng việc khai báo ToastContext nào.

    src/contexts/toast.js

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

    Toast Provider

    Trong React Context API có định nghĩa Provider để khi có sử thay đổi trong component thì các component bên trong có thể nhận biết sử thay đổi đó và phản anh kết quả thay đổi lên màn hình. Ở đây chúng ta cần hiển thị message lỗi mỗi khi có component nào đó thông báo về việc hiển thị message lỗi.

    import React, { useState } from 'react';
    import { Snackbar } from '@material-ui/core';
    import { Alert } from '@material-ui/lab';
    import { ToastContext } from '../contexts/toast';
    
    export const ToastProvider = (props) => {
      const { children } = props;
      const [state, setState] = useState({ isOpen: false });
    
      const show = (message) => {
        setState({ isOpen: true, message });
      };
    
      const hide = () => setState({ isOpen: false });
    
      const error = (message) => {
        show({ type: 'error', text: message });
      };
    
      const warn = (message) => {
        show({ type: 'warning', text: message });
      };
    
      const info = (message) => {
        show({ type: 'info', text: message });
      };
    
      const success = (message) => {
        show({ type: 'success', text: message });
      };
      const { isOpen, message } = state;
      return (
        <ToastContext.Provider
          value={{
            error: error,
            warn: warn,
            info: info,
            success: success,
            hide: hide
          }}>
          {children}
          {message && (
            <Snackbar open={isOpen} autoHideDuration={6000} onClose={hide}>
              <Alert elevation={6} variant="filled" onClose={hide} severity={message.type}>
                {message.text}
              </Alert>
            </Snackbar>
          )}
        </ToastContext.Provider>
      );
    };
    

    Sử dụng các hàm error, warn, info, success để hiển thị thông báo

    Để sử dụng được các hàm này trong ứng dụng bạn cần khai báo nó trong component chính của ứng dụng

    import React from 'react';
    import { ToastProvider } from './providers/ToastProvider';
    import { useToast } from './contexts/toast';
    import './App.css';
    
    function ButtonList() {
      const { error, warn, info, success } = useToast();
      return (
        <>
          <button onClick={() => error('error message!')}>error</button>
          <button onClick={() => warn('warn message!')}>warn</button>
          <button onClick={() => info('info message!')}>info</button>
          <button onClick={() => success('success message!')}>success</button>
        </>
      );
    }
    
    function App() {
      return (
        <ToastProvider>
          <ButtonList />
        </ToastProvider>
      );
    }
    export default App;
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết đã cung cấp cho các bạn thêm một các để hiển thị thông báo tốt hơn cho ứng dụng của bạn.

  • Lottie on Android (Part 3)

    Lottie on Android (Part 3)

    Xin chào các bạn, ở bài trước mình có giới thiệu cho các bạn về Animation Listener và Custom Animator với Lottie.
    Trong bài này mình sẽ giới thiệu cho các bạn về cách điều chỉnh các thuộc tính động. Bạn có thể điều chỉnh các thuộc tính động trong thời gian đang chạy của nó. Một số mục đích như:

    • Thay đổi chủ đề.
    • Thay đổi kích thước và thời gian.
    • Đáp ứng với những sự kiện lỗi hay thành công.

    Nội dung bài bao gồm:

    • Hiểu về After Effects để có thể vận dụng vào trong bài này.
    • Cần có những gì để thay đổi?
    • Cách thực hiện nó như thế nào?

    Hiểu về After Effects

    Để điều chỉnh các thuộc tính trong Lottie thì mình cần hiểu các thuộc tính đó.
    Các thuộc tính này được kế thừa từ các thuộc tính trong After Effects. Trong After Effects, nó là tập hợp các Layer ứng với mỗi một thời gian. Đối tượng trong Layer bao gồm: tên, màu sắc, kích thước … Lottie có thể tìm thấy các đối tượng và thuộc tính bằng KeyPath.

    Cần có những gì để thay đổi?

    Để thay đổi thuộc tính trong thời gian chạy, bạn cần có:

    • KeyPath
    • LottieProperty
    • LottieValueCallback

    KeyPath

    KeyPath được sử dụng với một nội dung cụ thể hoặc toàn bộ nội dung cần thay đổi. Nó được xác định bởi một danh sách các chuỗi tương ứng trong cấu trúc phân cấp của After Effects.

    KeyPath bao gồm tên cụ thể của nội dung hoặc ký tự đại diện:

    • Wildcard *: sử dụng để phù hợp với nội dung duy nhất ở vị trí của nó trong KeyPath.
    • Globstar **: sử dụng để phù hợp với không hoặc nhiều layer.

    KeyPath resolution

    KeyPath có khả năng lưu trữ một tham chiếu nội bộ đến nội dung mà họ quyết định. Khi bạn tạo một đối tượng KeyPath mới, nó sẽ không được quyết định. LottieDrawable và LottieAnimationView có một phương thức notifyKeyPath() lấy KeyPath và trả về một danh sách bằng 0 hoặc nhiều quyết định mà mỗi quyết định thành một phần nội dung bên trong. Điều này có thể được sử dụng để khám phá cấu trúc animation của bạn. Để làm như vậy, trong môi trường phát triển, new KeyPath("**") và ghi lại danh sách được trả về. Tuy nhiên, bạn không nên sử dụng ** với ValueCallback vì nó sẽ được áp dụng cho mọi phần nội dung trong animation của bạn. Nếu bạn quyết định KeyPath của mình và muốn thêm một giá trị callback, hãy sử dụng KeyPath được trả về từ phương thức đó vì chúng sẽ được giải quyết nội bộ và sẽ không phải tìm lại nội dung.

    LottieProperty

    LottieProperty là các thuộc tính có thể được set. Chúng tương ứng với giá trị trong After Effects.
    Bạn có thể tham khảo các thuộc tính ở đây.

    ValueCallback

    ValueCallback được gọi mỗi khi animation hoạt động. Nó cung cấp:

    • Khung bắt đầu của khung hình hiện tại.
    • Khung kết thúc của khung hình hiện tại.
    • Giá trị bắt đầu của khung hình hiện tại.
    • Giá trị kết thúc của khung hình hiện tại.
    • Giá trị progress từ 0 tới 1 của khung hình hiện tại với ngoài thời gian interpolation.
    • Giá trị progress của khung hình hiện tại với thời gian interpolator.
    • Progress trong tổng thể animation từ 0 tới 1.

    Ngoài ra, cũng có một số lớp con ValueCallback như LottieStaticValueCallback, nó nhận đúng một giá trị trong constructor và sẽ luôn trả về giá trị đó.

    ValueCallback classes

    • LottieValueCallback: đặt giá trị tĩnh trong contructor or override getValue().
    • LottieRelativeTYPEValueCallback: đặt giá trị trong constructor or override getOffset().
    • LottieInterpolatedTYPEValue: cung cấp giá trị bắt đầu, giá trị kết thúc và tuỳ chọn interpolator để có thời gian.

    Cách thực hiện nó như thế nào?

    Mình vẫn sử dụng trail-loading.jsonbài trước để điều chỉnh.
    Sau đó, bạn hãy xem các thuộc tính và phân cấp của nó:

    Có thể sử dụng log sau:

    loading_animation.resolveKeyPath(KeyPath("**")).forEach {
                Log.i("KeyPath", it.toString())
            }

    Hoặc, có thể sử dụng công cụ Lottie JSON Editor.
    (Với file mình sử dụng bên trên thì sẽ có 5 layer, còn các bạn…). Dưới đây sẽ là một vài ví dụ thay đổi.

    Thay đổi trong một layer

    Mình sẽ thay đổi màu Shape Layer 1("nm": "Shape Layer 1"), trong Ellipse 1("nm": "Ellipse 1") sang màu đỏ như sau:

    loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Fill 1"),
                LottieProperty.COLOR, { Color.RED }
            )
    Shape Layer 1

    Hoặc bạn có thể thay đổi màu sắc và kích thước của stroke như sau:

    loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Stroke 1"),
                LottieProperty.STROKE_COLOR, { Color.GREEN }
            )
    
            loading_animation.addValueCallback(
                KeyPath("Shape Layer 1", "Ellipse 1", "Stroke 1"),
                LottieProperty.STROKE_WIDTH, { 20f }
            )
    Stroke

    Sử dụng Wildcards

    Mình sử dụng Wildcards để thay đổi toàn bộ layer có Ellipse 1("nm": "Ellipse 1") sang màu GREEN như sau:

    loading_animation.addValueCallback(
                KeyPath("*", "Ellipse 1", "Fill 1"),
                LottieProperty.COLOR, { Color.GREEN }
            )
    Wildcards

    Sử dụng Globstars

    Mình sử dụng Globstars để thay đổi toàn bộ layer kết thúc thuộc tính Fill 1 sang màu BLUE như sau:

    loading_animation.addValueCallback(
                KeyPath("**", "Fill 1"),
                LottieProperty.COLOR, { Color.BLUE }
            )
    Globstars

    Các bạn có thể tham khảo các thuộc tính có thể thay đổi dưới đây:

    Transform:

    • TRANSFORM_ANCHOR_POINT
    • TRANSFORM_POSITION
    • TRANSFORM_OPACITY
    • TRANSFORM_SCALE
    • TRANSFORM_ROTATION

    Fill:

    • COLOR (non-gradient)
    • OPACITY
    • COLOR_FILTER

    Stroke:

    • COLOR (non-gradient)
    • STROKE_WIDTH
    • OPACITY
    • COLOR_FILTER

    Ellipse:

    • POSITION
    • ELLIPSE_SIZE

    Polystar:

    • POLYSTAR_POINTS
    • POLYSTAR_ROTATION
    • POSITION
    • POLYSTAR_OUTER_RADIUS
    • POLYSTAR_OUTER_ROUNDEDNESS
    • POLYSTAR_INNER_RADIUS (star)
    • POLYSTAR_INNER_ROUNDEDNESS (star)

    Repeater:

    • All transform properties
    • REPEATER_COPIES
    • REPEATER_OFFSET
    • TRANSFORM_ROTATION
    • TRANSFORM_START_OPACITY
    • TRANSFORM_END_OPACITY

    Layers:

    • All transform properties
    • TIME_REMAP (composition layers only)

    Bài này đến đây là kết thúc rồi, hãy đón đọc bài viết tiếp theo của mình bạn nhé. Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^

  • Lottie on Android (Part 2)

    Lottie on Android (Part 2)

    Xin chào các bạn, ở bài trước mình có giới thiệu cho các bạn về Lottie và cách sử dụng Lottie cho Android.
    Trong bài này mình sẽ giới thiệu cho các bạn về Animation Listener và Custom Animator với Lottie.

    Animation Listener

    Có rất nhiều trường hợp khi sử dụng Lottie mà chúng ta cần phải xử lý các công việc khác nữa. Dưới đây là một vài trường hợp cụ thể:

    • Mở một màn hình mới sau khi kết thúc chạy animation với Lottie.
    • Cập nhật giá trị trong khi đang chạy animation với Lottie.
    • Điều chỉnh tốc độ hay thời gian chạy của animation.

    Listener được mô tả như sau:

    loading_animation.addAnimatorUpdateListener { valueAnimator ->
                //do something
            }

    Tham số valueAnimator bên trên thực chất nó là tham số số trong ValueAnimator Class ở Android SDK. Nó cung cấp cho bạn biết về trạng thái hiện tại và thời gian hiện tại của animation.

    Bây giờ, tôi sẽ có 1 bài toán nho nhỏ như sau: ở bài trước tôi sử dụng loading với Lottie, bài này tôi sẽ thực hiện khi loading thì sẽ thực hiện cùng ProgressBar và set giá trị của progress đó hiển thị trên màn hình.

    Tôi sẽ thiết kế layout như sau:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <ProgressBar
            android:id="@+id/progress_horizontal"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:max="100"
            android:progress="0"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintWidth_percent="0.9" />
    
        <TextView
            android:id="@+id/progress_number"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textColor="@android:color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/progress_horizontal"
            app:layout_constraintWidth_percent="0.9"
            tools:text="0/100" />
    
        <com.airbnb.lottie.LottieAnimationView
            android:id="@+id/loading_animation"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </androidx.constraintlayout.widget.ConstraintLayout>

    Sau đó, tôi sẽ thực hiện lấy giá trị và set cho ProgressBar như dưới đây:

    package techover.lottie
    
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import kotlinx.android.synthetic.main.activity_main.*
    
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            loading_animation.setAnimation("trail-loading.json")
            loading_animation.loop(true)
            loading_animation.speed = 0.5f
            loading_animation.addAnimatorUpdateListener { valueAnimator ->
                val progress = (valueAnimator.animatedValue as Float * 100).toInt()
                progress_horizontal.progress = progress
                progress_number.text = "$progress / 100"
            }
            loading_animation.playAnimation()
    
        }
    }
    Animation Listener

    Custom Animator

    Để kết hợp Lottie vào ứng dụng để có những hình ảnh mượt mà, sinh động thì thật đơn giản phải không nào? 😉
    Nhưng sẽ có nhiều trường hợp việc kết hợp cũng trở nên quan ngại phần nào: ví dụ như kết hợp Lottie khi download, khi scroll position hay cử chỉ… Vì vậy chúng ta phải Custom Animator để cho phù hợp với từng bài toán.
    Ví dụ sau sẽ giúp các bạn hình dung dễ hơn:

    //Custom animation speed or duration.
            val animator = ValueAnimator.ofFloat(0f, 2f)
            animator.addUpdateListener { valueAnimator: ValueAnimator ->
                loading_animation.speed = valueAnimator.animatedValue as Float
            }
            animator.start()

    Ở đây, mình đã thực hiện điều chỉnh tốc độ của loading chạy từ 0 -> 2 bằng việc sử dụng Animator.

    Custom Animator

    Bài tiếp theo mình sẽ giới thiệu làm cách nào để điều chỉnh các thuộc tính động, sẽ có nhiều cái thú vị đấy, hãy đón đọc bài viết của mình bạn nhé.

    Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^

  • Lottie on Android (Part 1)

    Lottie on Android (Part 1)

    Xin chào các bạn hi hi, lại là mình đây. 🙂
    Để thay đổi không khí sau loạt bài về animation, hôm nay mình sẽ giới thiệu với các bạn về Lottie cho Android.
    Sau những loạt bài về animation thì các bạn có thấy ứng dụng của chúng ta đã trở nên đẹp và sinh động hơn chưa?. Tôi nghĩ chắc chắn là rồi phải không? 🙂
    Nhưng sẽ có nhiều vị khách khó tính thì vẫn có chút xíu chưa hài lòng về độ mượt mà của animation. Bạn đừng lo, Lottie sẽ giải quyết vấn đề đó cho bạn ngay.
    Ứng dụng của bạn sẽ trở nên mượt mà, sinh động và đẹp hơn rất nhiều nữa đấy. Nghe đến đây thì bạn đã hào hứng để tìm hiểu nó rồi chứ. Let’s go…

    Trong bài này mình sẽ giới thiệu đến các bạn những mục sau:

    • Giới thiệu về Lottie
    • Cách sử dụng Lottie cho Android

    Giới thiệu về Lottie

    Lottie là một mã nguồn mở về animation được xây dựng bởi Airbnb. Nó có thể dùng được ở Android(hỗ trợ android từ phiên bản JellyBean API 16), iOS, React Native hay cả Web. Về bản chất hoạt động thì nó sẽ parse animation từ Adobe After Effects, thông qua Bodymovin và được xuất ra định dạng json. Sau đó, các nhà phát triển của các platform sẽ sử dụng công cụ thư viện Lottie để các animation sẽ được hiển thị tương ứng trên các platform.

    Cách sử dụng Lottie cho Android

    Đầu tiên, bạn chuẩn bị cho mình file có định json (được xuất ra từ Adobe After Effects, thông qua Bodymovin như bên trên mình đã chia sẻ).
    Thường thì cái này được design team của các dự án cung cấp cho bạn hoặc bạn tự tạo đều được.
    Bạn cũng có thể tham khảo ở đây rất nhiều.
    Tôi đã chuẩn bị cho mình trail-loading.json cho bài viết này.

    Sau khi có được file định dang json bên trên thì tiếp đến bạn sẽ tạo project và thêm vào trong build.gradle một dependencies như dưới đây:

    dependencies {
        implementation 'com.airbnb.android:lottie:3.4.0'
    }

    Bạn sẽ tạo assets folder (app/src/main/assets) và copy file có định dạng json bên trên vào nhé. Còn mình sẽ copy file trail-loading.json của mình vào.
    Tiếp theo bạn sẽ thêm animation vào xml với layout tương ứng để bạn hiển thị. Ở đây, mình sẽ thêm vào xml trên activity của mình.

    <?xml version="1.0" encoding="utf-8"?>
    <com.airbnb.lottie.LottieAnimationView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/loading_animation"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:lottie_autoPlay="true"
        app:lottie_fileName="trail-loading.json"
        app:lottie_loop="true"
        app:lottie_speed="1" />

    Các thuộc tính mà chúng ta sử dụng bên trên:

    • app:lottie_autoPlay="true": bắt đầu chạy animation.
    • app:lottie_fileName="trail-loading.json": sử dụng file json ở trong thư mục assets mà bạn đã thêm.
    • app:lottie_loop="true": cho phép animation được lặp.
    • app:lottie_speed="1": tốc độ chạy animation.
      Ngoài ra cũng có các thuộc tính sau nữa: lottie_scale, lottie_repeatCount, lottie_repeatMode

    Bên trên là bạn đã thêm animation vào trong xml, nhưng nó cũng có thể được thực hiện, được set các thuộc tính ở onCreate() / onCreateView() bạn nhé.

    package techover.lottie
    
    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import kotlinx.android.synthetic.main.activity_main.*
    
    class MainActivity : AppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            loading_animation.setAnimation("trail-loading.json")
            loading_animation.loop(true)
            loading_animation.speed = 1f
            loading_animation.playAnimation()
        }
    }

    Thật đơn giản phải không các bạn? Giờ thì xem thành quả của bạn vừa làm nhé.

    Trail-Loading

    Đến đây, các bạn sẽ đặt câu hỏi rằng: tôi có thể điều khiển và lắng nghe nó không? (think)
    Oh, Tất nhiên là được rồi. Bài tiếp theo mình sẽ giới thiệu, hãy đón đọc bài viết của mình bạn nhé.

    Cảm ơn các bạn đã dành thời gian đọc bài của mình. Rất mong nhận được sự góp ý từ các bạn ^^

  • Passing Data with Callback Swift

    Passing Data with Callback Swift

    Một trong những vấn đề cơ bản trong lập trình ứng dụng là truyền data giữa các view controller. Có rất nhiều cách để làm điều này, như là dùng protocol, notification,… Ở bài viết này, mình sẽ giới thiệu đến 1 cách nữa, đó là sử dụng callback.
    Bài viết này yêu cầu sự hiểu biết về closure. Nếu chưa hiểu về closure, bạn có thể đọc tại đây:

    Nội dung bài viết

    • Function type
    • Callback là gì
    • Lợi ích của callback
    • Truyền data bằng cách sử dụng callback.

    Function type:

    Mọi function đều có 1 kiểu dữ liệu cụ thể, được tạo bởi kiểu dữ liệu của các tham số truyền vàokiểu dữ liệu trả về của function đó.

    Ví dụ ở func trên, không có tham số truyền vào và không có kiểu dữ liệu trả về, nên function type của func trên là () -> ().

    Đối với func không có kiểu trả về, thì cũng có thể viết theo cách khác là func đó trả về kiểu Void.

    func calculate(a: Int, b: Int) -> Int {
        return a + b
    } 
    // Function này có kiểu dữ liệu là (Int, Int) -> Int
    

    Ở bài viết lần này, mình sẽ không nói sâu về function type, mà sẽ tập trung vào chủ đề passing data bằng callback. Vậy callback là gì?

    Callback là gì?

    • Callback có thể hiểu như là 1 closure được gán cho 1 biến.
    • Để sử dụng callback truyền data, bạn khai báo callback với kiểu dữ liệu là kiểu dữ liệu của data mà bạn muốn truyền đi.
    var onCalculate: (Int, Int) -> (Int)

    Ở trên là ví dụ cách khai báo 1 callback có kiểu dữ liệu (Int, Int) -> Int. Callback này sẽ truyền data là 2 tham số kiểu Int đi, và khi 1 nơi khác nhận được data này, nó sẽ xử lí data và trả về 1 kiểu Int.

    var onPrint: (String) -> Void

    Ở ví dụ này, callback sẽ truyền data kiểu String đi, và khi 1 nơi khác nhận được dữ liệu, nó sẽ xử lí dữ liệu theo kiểu Void.

    Note: Nếu muốn truyền đi dữ liệu kiểu khác, bạn chỉ cần đơn giản sửa callback thành kiểu dữ liệu bạn mong muốn truyền đi.

    Truyền data sử dụng callback

    Giả sử có 2 view controller như sau:

    • View Controller 1 có 1 label để hiện thị kết quả, và 1 button để push sang View Controller 2.
    • View Controller 2 có nhiệm vụ thực hiện tính toán tổng của 2 số kiểu Int và trả kết quả kiểu Int về cho View Controller 1 để hiển thị. -> VC2 muốn truyền đi 1 data kiểu Int.
    • View Controller 1 sau khi nhận được data của VC2, sẽ hiển thị lên label.

    VC1:

    class FirstViewController: UIViewController {
        @IBOutlet weak var myLabel: UILabel!
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
        
        @IBAction func didTapButtonGoNext(_ sender: Any) {
            let storyboard = UIStoryboard(name: "Main", bundle: nil)        
            let secondVC = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
            
            // Viết code hiển thị kết quả ở đây
            
            navigationController?.pushViewController(secondVC, animated: true)
            
        }
    }

    VC2:

    class SecondViewController: UIViewController {
        // 1
        var completionSum: ((Int) -> (Void))?
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            // 3
            let result = calculate(num1: 9, num2: 8)
            completionSum?(result)
            
            // 4
            navigationController?.popViewController(animated: true)
        }
        
        // 2
        func calculate(num1: Int, num2: Int) -> Int {
            return num1 + num2
        }
    }
    1. Khởi tạo 1 callback kiểu (Int) -> (Void), tức là callback này sẽ truyền đi data kiểu Int, và khi nhận được data sẽ xử lí theo kiểu Void.
    2. Khai báo func calculate để tính tổng 2 số và trả về kết quả kiểu Int.
    3. Tính tổng 2 số 9 và 8. Gọi completionSum?(result) để truyền đi result.
      Ở đây có dấu ? ở callback vì callback này là kiểu optional. Khi callback chưa được khởi tạo thì trình biên dịch sẽ bỏ qua mà không gọi callback. Vì vậy hãy chắc chắn rằng bạn đã khởi tạo callback trước khi gọi chúng.
    4. Back về VC1 để hiển thị kết quả.

    Giờ thì quay trở lại VC1, add thêm đoạn code sau vào phần để trống để khởi tạo completionSum cho VC2, bằng cách gán completionSum bằng 1 closure có cùng kiểu dữ liệu.

    secondVC.completionSum = { [weak self] (result) in
        self?.myLabel.text = String(result)
    }

    Ở đây bạn đã viết đoạn code để xử lí dữ liệu nhận được từ VC2. Sau khi nhận được result kiểu Int, bạn sẽ xử lí dữ liệu theo kiểu Void bởi kiểu dữ liệu của callback là (Int) -> Void.

    Khi VC2 gọi completionSum để truyền result đi, VC1 sẽ ngay lập tức nhận được và gọi hàm update label ở trên. Trình biên dịch sẽ chạy theo trình tự như sau:

    • Bấm vào button 1 -> Khởi tạo VC2, khởi tạo completionSum cho VC2 -> push sang VC2.
    • VC2 tính toán kết quả -> gọi completionSum -> VC1 nhận được kết quả và update cho label -> VC2 pop về VC1 để hiển thị kết quả.

    Ngoài ra bạn có thể khởi tạo callback bằng cách gán callback bằng 1 func có sẵn có cùng kiểu dữ liệu.
    Ở VC1, khai báo 1 func như sau:

    func showResult(result:  Int) {
        myLabel.text = String(result)
    }

    Func này có kiểu dữ liệu (Int) -> Void, cùng kiểu dữ liệu với callback completionSum.
    Sửa lại đoạn khai báo completionSum ở VC1 thành như sau:

    secondVC.completionSum = showResult

    Cách làm này cùng tác dụng với cách khai báo ở trên nhưng sẽ làm cho func ngắn gọn và clear hơn.

    Lợi ích của việc dùng callback

    • Đối với việc chỉ cần truyền những data đơn giản, xử lí đơn giản thì có thể dùng callback chứ không phải tạo protocol, notification,…
    • Tính reuseable cao, và ngắn gọn.
  • Kiểm thử tự động cùng Robot Framework dành cho tester

    I. Giới thiệu

    1. Tổng quan về Robot Framework

    Robot Framework là một testing framework. Nó cung cấp mọi thứ cần thiết để xây dựng và phát triển một kịch bản kiểm thử, gồm điều kiện đầu vào/kết thúc, báo cáo kết quả, … Điểm hấp dẫn của Robot Framework với các tester chính là chúng ta không cần quan tâm đến các thuật toán lập trình cơ bản nhất. Mọi thứ chúng ta cần làm chính là viết ra một kịch bản kiểm thử dựa trên các từ khóa (keyword) mà thôi.

    Cụ thể hơn, Robot Framework là:

    • Framework dùng để kiểm thử, cung cấp nền tảng kiểm thử cho tester dựa trên ngôn ngữ lập trình Python. Cách tiếp cận của nền tảng kiểm thử này là hướng từ khoá (keyword driven) và hướng dữ liệu (data driven) dành cho việc kiểm thử để nghiệm thu sản phẩm ngay từ đầu (end-to-end acceptance testing).
    • Để tiếp cận nền tảng kiểm thử này, tester chỉ cần viết kịch bản kiểm thử theo hướng từ khóa (keyword driven) và hướng dữ liệu (data driven).
    • Tester có thể tạo các từ khóa cấp cao mới từ những cái hiện có bằng cách sử dụng cú pháp tương tự được sử dụng để tạo ra các trường hợp thử nghiệm.

    Các tính năng nổi bật của Robot Framework:Những việc làm hấp dẫn

    • Robot Framework giúp chúng ta thực hiện kiểm thử tự động với kịch bản ở dạng bảng một cách dễ dàng. Robot Framework đưa ra kết quả thực thi các kịch bản kiểm thử và các log ở dạng html, giúp chúng ta đọc và phân tích kết quả nhanh chóng và dễ dàng hơn.
    • Robot Framework có hỗ trợ chức năng đánh dấu các kịch bản kiểm thử, cho phép chúng ta lựa chọn kịch bản kiểm thử tiện lợi và nhanh chóng.
    • Thế mạnh lớn nhất của Robot Framework chính là khả năng chạy trên nhiều hệ điều hành khác nhau mà không cần chỉnh sửa kịch bản kiểm thử hay các từ khóa ở tầng dưới.

    2. Các thư viện hỗ trợ trong Robot Framework

    Robot Framework có rất nhiều thư viện hỗ trợ cho việc kiểm thử tự động, có thể tham khảo các thư viện dành cho Robot Framwwork tại http://robotframework.org/#test-libraries .

    7c09fc7530966020cdd4ebce92806688bee90a11

    Tuy nhiên, trong nội dung bài viết này, chúng tôi sẽ tập trung giới thiệu 2 thư viện phổ biến nhất đó là Selenium2Library và Calculator Library.

    2.1 Selenium2Library

    • Selenium2Library được sử dụng để kiểm thử trên nền Web, và được fork từ SeleniumLibrary và được bổ sung để sử dụng Selenium 2 và WebDriver.
    • Selenium2Library hoạt động ở hầu hết các trình duyệt hay dùng như IE, Firefox, Safari, Chrome, … và có thể được dùng với cả Python và Jython.

    Để chạy các testcase bằng cách sử dụng Selenium2Library, trước tiên bạn cần:

    1. Cài đặt Selenium2Library,
    2. Import Selenium2Library vào các testsuite Robot.
    3. Dùng từ khóa Open Browser để bật trình duyệt muốn dùng kiểm thử.

    Tại sao nên sử dụng thư viện Selenium2Library?

    Selenium2Library là ngôn ngữ rất sát với ngôn ngữ thực tế của người dùng, bạn mong muốn action gì bạn chỉ cần gõ từ khóa tương ứng.

    Ví dụ:

    • input text: nhập chuỗi ký tự
    • click button: nhấp chuột
    • double click element: nhấp đôi chuột vào element
    • get alert message: lấy giá trị của thông báo
    • open context menu: mở các menu con
    • v.v…

    Tìm hiểu thêm tại:

    2.2 CalculatorLibrary

    Tiếp theo ta sẽ tìm hiểu về một thư viện được có sẵn trong Robot Framework, một thư viện về tính toán đơn giản, nó chỉ chứa logic nghiệp vụ chứ không bao gồm phần UI.

    Chúng ta cùng đi qua một ví dụ về CalculatorLibrary và cùng run một test case đơn giản để có thể hiểu hơn về Robot Framework.

    Bạn có thể tải bản demo tại đây: https://bitbucket.org/robotframework/robotdemo/downloads.

    Ví dụ:

    b94e442c87eff8f32e9e0c55418f3c5f7c165569

    Ví dụ trên bao gồm 5 Testcases: Trong đó có 4 TCs ví dụ trường hợp PASSED, và 1 TC ví dụ về trường hợp FAILED:

    • Push button PASSED
    • Push multiple buttons FAILED
    • Simple calculation PASSED
    • Longer calculation PASSED
    • Clear PASSED

    Các keyword thì khá đơn giản và dễ hiều nên mình không giải thích gì thêm. Bây giờ chúng ta sẽ chạy thử 5 TCs này bằng Terminal trên Linux như sau:

    1. Download thư viện và có sẵn tại: https://bitbucket.org/robotframework/robotdemo/downloads.
    2. Mở terminal và trỏ đến folder chứa file vừa download.
    3. Run Test case “keyword_driven.robot” bằng command sau: $ robot keyword_driven.robot.
    a5fabe04a9d9d31297524951b7ba35c005bd9856

    Kết quả là 4 TCs Pass và 1 TC Failed.

    Kết luận: Đối với Robot Framework chúng ta không cần phải biết lập trình để viết testcase và script như những công cụ khác. Đối với những yêu cầu đơn giản và nhanh chóng thì Robot Framework là một sự lựa chọn phù hợp.

    II. Cài đặt

    Phần này sẽ hướng dẫn cách cài đặt RF cùng với Selenium trên Linux và Windows.

    1. Cài đặt Robot Framework

    Bước 1. Cài đặt Python.

    Trước hết, vì Robot Framework là một nền tảng kiểm thử dựa trên nền tảng Python, nên trước tiên cần cài đặt Python (nên cài Python 2.5 hoặc mới hơn – khuyến cáo cài đặt Python 2.7).

    Linux: Python thường đi kèm với cài đặt Ubuntu/Linux. Để kiểm tra xem Python đã cài đặt chưa, cũng như phiên bản của nó, dùng câu lệnh sau trên Terminal (có thể dùng phím tắt Ctrl+Alt+T để bật Terminal: $ python –version. Nếu Python đã được cài đặt, bạn sẽ nhìn thấy phiên bản của nó, chẳng hạn Python 2.7.6.

    Windows: Tương tự như trên Linux, bạn hãy bật cmd lên và kiểm tra xem python đã được cài đặt chưa bằng lệnh: python –version. Nếu chưa, đi đến https://www.python.org/ và tải phiên bản Python tương ứng và cài đặt nó.

    Bước 2. Cài đặt PIP (Python Package Manager).

    Linux: PIP là một package manager cho việc thiết lập các gói Python. Để cài đặt PIP, dùng câu lệnh sau:

    $ wget https://bootstrap.pypa.io/get-pip.py

    $ sudo python get-pip.py

    Windows: PIP đã được cài đặt nếu bạn đang dùng phiên bản Python 2 >=2.7.9 hoặc Python 3 >=3.4 tải xuống từ https://www.python.org/, tuy nhiên bạn sẽ cần nâng cấp PIP bằng lệnh sau:

    python -m pip install -U pip

    Bước 3. Cài đặt gói Robot Framework bằng cách sử dụng PIP.

    Linux: Dùng câu lệnh sau để cài đặt Robot Framework:

    $ sudo pip install robotframework

    Phiên bản mới nhất của Robot Framework sẽ được cài tự động. Nếu muốn cài một phiên bản cụ thể, chỉ việc thêm vào, chẳng hạn:

    $ sudo pip install robotframework==2.8.4

    Sau khi cài đặt hoàn tất, dùng câu lệnh sau để xem việc cài đặt đã thành công chưa: $ pybot –version. Bạn sẽ nhìn thấy thông tin phiên bản Robot Framework nếu thiết lập thành công.

    Windows: Tại cửa sổ Command promt, chuyển tới thư mục cài đặt Python và dùng lệnh sau để cài đặt RF:

    pip install robotframework

    Sau khi cài đặt thành công, dùng pybot –version để kiểm tra:

    85a54dbbe7e47d670d9649022a42a348851f4d99

    2. Cài đặt Selenium2Library

    Để làm việc với Webdriver (Selenium2) và Robot Framework, bạn cần cài đặt Selenium2Library bằng cách sử dụng PIP:

    Linux: Dùng câu lệnh sau: $ sudo pip install robotframework-selenium2library

    Câu lệnh này sẽ tự động cài các dependency của nó, gồm decorator, và các gói Selenium. Ngay sau khi hoàn tất cài đặt, dùng câu lệnh $ python để chuyển đến cửa sổ của python:

    086ea3973c728c0a8dc436a8df6705c44611876a

    Ở cửa sổ này, gõ câu lệnh sau để import Selenium2 Library: >> import Selenium2Library.

    Nếu không thấy lỗi nào bắn ra nghĩa là thư viện Selenium2 đã được cài thành công. Nếu muốn thoát khỏi cửa sổ Python, dùng lệnh exit().

    Windows: Tại cửa sổ Command promt, dùng lệnh: pip install robotframework-selenium2library. Sau khi cài đặt thành công sẽ có thông báo như sau:

    e0077af87537efcafad673aec2cde3c38638274d

    3. Cài đặt RIDE (Standalone RobotFramework Test Data Editor)

    RIDE là một IDE để xây dựng kiểm thử bằng cách sử dụng Robot Framework. Ngoài RIDE ra, bạn có thể thay thế bằng SublimeText, IntelliJ hay Eclipse, … Vì RIDE được phát triển bằng cách sử dụng wxPython nên bạn cần cài bộ tool wxPython 2.8 có hỗ trợ unicode để chạy RIDE. Cụ thể như sau:

    Linux: Dùng câu lệnh sau để cài wxPython:

    $ sudo apt-get install python-wxgtk2.8

    $ sudo apt-get install python-wxversion

    Tiếp theo, dùng câu lệnh sau để cài RIDE:

    $ sudo pip install robotframework-ride

    Để xác minh xem việc cài đặt đã OK chưa, chạy câu lệnh sau: $ ride.py. Ứng dụng RIDE sẽ bật lên như sau:

    072281cc7ba1c61ffa0c24a1f95ea0756e379105

    Windows:

    c92c89abad3eba6ebfaac1b799ae6387919069e0
    3a74e904945698ffa13d88086b6bd922e79fc89d
    • Tại cửa sổ Command promt, dùng lệnh: pip install robotframework-ride để cài đặt RIDE.
    • Sau khi cài đặt thành công sẽ có thông báo như sau
    eb3219bf61baabb75d7a4a35b0a5111ebea0097c
    • Dùng lệnh: ride.py để khởi động RIDE.

    III. Cách bắt phần tử giao diện

    1. Tổng quan XPath

    • XPath là một cách để phân tích mã HTML nhằm xác định các yếu tố của một web driver.
    • Là ngôn ngữ hỗ trợ tìm kiếm thông tin trong tài liệu XML qua việc sử dụng biểu thức XPath để định hướng tìm kiếm dữ liệu trên XML thay vì phải thực hiện tìm kiếm đệ qui để duyệt cây XML.
    • Xpath định nghĩa 7 loại nodes theo mô hình thể hiện bên dưới từ root, element, attribute, text, namespace, processing-instruction và comment.
    • Ngoài ra, Xpath còn định nghĩa một số node đặc biệt để thể hiện mối quan hệ giữa các node trong mô hình trong quá trình xử lý như sau:
    1. Parent Node: node trên trực tiếp của node hiện hành.
    2. Child Node: tập node trực tiếp của node hiện hành cấp thấp hơn.
    3. Sibling: node ngang hàng hay cùng cha với node hiện hành.
    4. Ancestors: tất cả node con bên trên node hiện hành cùng nhánh.
    5. Descendants: tất cả node con bên dưới của node hiện hành cùng nhánh.

    Cú pháp của XPath:

    • Để truy vấn với đường dẫn tuyệt đối nghĩa là đi từ root của tài liệu XML đến các thành phần cần truy cập, XPath qui định với cú pháp bắt đầu bằng dấu /
    • Để truy vấn với đường dận tương đối để có thể truy cập đến thành phần bất kỳ thỏa điều kiện, XPath qui định cú pháp sử dụng với dấu //
    • Để truy vấn đến một thành phần bất kỳ mà không cần biết tên của nó là gì, XPath qui định ký tự sử dụng là *.
    • Để truy cập thuộc tính của một node, XPath qui định thuộc tính truy vấn phải có cú pháp bắt đầu là @.Ví dụ @tênThuộcTính.
    • Điều kiện khi truy vấn được đặt trong dấu []
    • Truy vấn lựa chọn nodes
    Biểu thứcĐịnh nghĩa
    tênNodeChọn tất cả các node con của tênNode.
    /Chọn tất cả các node tính từ root.
    //Chọn tất cả node tính từ node hiện hành.
    .Chọn node hiện hành.
    ..Chọn node cha của node hiện hành.

    Các phép toán được sử dụng trong XPath:

    • Đại số: +, -, * (nhân), div (chia thập phân), mod (chia lấy dư)
    • So Sánh hay quan hệ: =, != (khác), <, <=, >, >=
    • Luận lý: true, false, and, or, not
    • Kết hợp: | (hội)

    2. Cách bắt XPath bằng Firebug và FirePath

    Firebug và FirePath là 2 add-ons hỗ trợ cho việc bắt XPath nhanh và dễ dàng hơn trên Firefox browser.

    Cài đặt Firebug và FirePath:

    1. Trên Firefox browser, chọn icon [Open Menu] -> Add-ons.
    2. Tìm và cài đặt Firebug, FirePath.

    Sau khi cài đặt thành công, Firebug và FirePath xuất hiện trong mục Extensions và các icon của chúng sẽ xuất hiện trên thanh công cụ của Firefox.

    2b510b64cc5c74744898385a5d3c4895ab138f7f

    Sử dụng Firebug và FirePath:

    • Nhấp vào icon con bọ -> chọn thẻ FirePath.
    • Nhấp vào ký hiệu con mũi tên bên cạnh con bọ -> tiếp nhấp chuột vào element cần lấy xpath. xpath của element đó sẽ hiển thị:
    d1fbc9c3854ef35c3353076e1ba16bd7c621a5da

    Lưu ý:

    • XPath lấy được từ FirePath chỉ mang tính chất tham khảo và giúp người dùng xác định phần tử dễ dàng hơn.
    • XPath lấy được từ FirePath là cách đơn giản nhất nhưng lại chưa đảm bảo tính ổn định và duy nhất khi version của web page thay đổi. Vậy nên, người dùng có thể sử dụng một số cách hỗ trợ truy vấn sau để bắt XPath nhằm tăng tính ổn định và khả dụng khi muốn sử dụng các element liên quan đến nhau một cách chính xác hơn:
    AxisĐịnh nghĩa
    ancestorChọn tất cả các node trên của node hiện hành.
    ancestor-or-selfChọn tất cả các node trên của node hiện hành và chính nó.
    attributeChọn tất cả các thuộc tính của node hiện hành.
    childChọn node con của node hiện hành.
    descendantChọn tất cả các node dưới của node hiện hành.
    descendant-or-selfChọn tất cả các node dưới của node hiện hành và chính nó.
    followingChọn tất cả các node sau khi tag đóng của node hiện hành.
    following-siblingChọn tất cả các node ngang cấp sau khi tag đóng của node hiện hành.
    namespaceChọn tất cả namespace của node hiện hành.
    parentChọn tất cả node cha của node hiện hành.
    precedingChọn tất cả các thành phần trước khi bắt đầu tag mở của node hiện hành.
    preceding-siblingChọn tất cả các node ngang hàng trước khi bắt đầu tag mở của node hiện hành.
    selfChọn node hiện hành.

    Ví dụ:

    Vd1: ancestor

    1. Hiển thị tất cả các thẻ cha có chứa thẻ div id=<”identifier-shown”>, ko bao gồm thẻ div id=<”identifer-shown”> được inspect từ trang Login Gmail.
    fb1b340719938f5b70b42e784bf1d152f32404ab
    1. Nếu chỉ muốn hiển thị thẻ cha được chỉ định.
    347ef0cedd9f6ff09d2cfba5d6a3db99e50b6898

    Vd2: ancestor-or-self

    Hiển thị các thẻ cha và cả thẻ div id=<”identifier-shown”>, có bao gồm thẻ div id=”<identifier-shown>” được inspect từ trang Login Gmail.

    278d8b8f2a7b923f6b3c28327e8823d4741457e2

    Vd3: attribute

    Chọn tất cả các attribute hiện hành của nút Next ở trang Login Gmail.

    b5ad5ea2eab13aed28dea354e9437a6a3032919b

    Vd4: child

    Chọn tất cả các thẻ con của thẻ div class=”input-wrapper focused”.

    6152b5513962f8991e981c2b0f2cbe8575b96257

    Vd5: descendant

    Chọn tất cả các thẻ con và cháu của thẻ div id=”input-wrapper focused”, ko bao gồm thẻ div id=”input-wrapper focused”.

    96aa0fe73cb7df41d982193eb3b52dc4bbc60eeb

    Vd6: descendant-or-self

    Chọn tất cả các thẻ con và cháu của thẻ div id=”input-wrapper focused”, bao gồm cả thẻ div id=”input-wrapper focused”.

    374b38776e68e15f57e36fd056c93f70f6f47757

    Vd7: following

    1. Hiển thị tất cả các thẻ sau thẻ đóng của thẻ div class=”identifier-wrapper focused”.
    f82c138b5de9639827b4a1a9230339c221f390ba
    1. Hiển thị 1 thẻ sau thẻ đóng của thẻ div class=”identifier-wrapper focused”.
    9566b8874419e55d44286a49173ddf7cd713a883

    Vd8: following-sibling

    Hiển thị các thẻ sibling sau thẻ đóng của thẻ div class=”input-wrapper focused”.

    ca6db17bf580a6fc4d1afbb2ffc7759414d04c8c

    Vd9: parent

    Chọn tất cả thẻ cha của thẻ div class=”input-wrapper focused”.

    16d6dcb79d278326f232eee009cfbe09f898f5d1

    Vd10: preceding

    Chọn tất cả các thẻ trước thẻ input id=”next”, ngoại trừ các ancestor và attribute.

    b328b438090e7a67f184b8973b6b954530b9d395

    Vd11: contains()

    Chọn tất cả các phần tử có chứa text là “Create”.

    db1ce283d67472980df94cf6a90833ebe736b22a

    IV. Demo

    Ngữ cảnh: Đăng nhập vào trang http://www.chatwork.com/ thành công.

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

    Bước 1. Tạo một Project mới.

    • i. Nhấp chọn File –> New Profile.
    • ii. Nhập Name và chọn Type – Directory, Format – HTML.
    211ec6ddfbb1457c6d91357e968da51ff864779c

    Bước 2. Tạo Interface.

    • i. Nhấp phải chọn Project folder và chọn Test Suite.
    a75a1823bf1388333107559ca6b994df0fa5eda6

    ii. Đặt tên “Interface”.

    0530a02b087c4672f7252509de509e2a8c87ddf5

    iii. Nhấp phải chọn thư mục Interface –> New Resource.

    562c945bba4f5e3da63be958a413d6f2940b929a
    bcfa6c3d571a0987751ec8afd4936b0e3f1020bb

    iv. Nhấp phải chọn Interface resource –> New Scalar.

    d988bc7f6a51bed3d96e8202bfb69cf5bba18661
    337f06616c72772a4fcdf662e1e8e9a625f9b61f

    Bước 3. Tạo New Action.

    • i. Nhấp phải chọn Project Name –> New Suite.
    • ii. Tạo mới suite “Action”.
    273b7ee418507ff200fa1c86a1ba1862e256cf38

    iii. Tương tự như Interface, tạo New Source cho Action bằng cách nhấp phải chọn “Action”.

    e2718e1f236521b4a4b718515505604f222ce433
    1d1be3475039d40ae42c3f63572545128b1331ae

    iv. Thêm New User Keyword.

    c8d46e495b8f5950bae913cf471aea9828d1b6a5
    b0d36811a6a32695ea6475acf74008c3b6b67089

    v. Thêm Selenium2Library cho mỗi action.

    a7693ebb2f54ea76a63d8d8a0c28150b2a47c07b

    vi. Import Interface của page tương ứng vào bằng cách bấm Resource và trỏ đến thư mục chứa Interface.

    2382ecdba71ea77a1292701f1e9fe93a7256247a

    vii. Tạo các bước thực thi kiểm thử:

    dccb5b180a148c84e80df86b0af263a09d9fd5c8

    Bước 4. Tạo Testsuite.

    • i. Nhấp phải chọn thư mục dự án, chọn “New Suite”. Tạo Test Suite bằng File txt.
    a10719a6e9e839600ff05d04ef1ea03215334f40
    e22a1270422cc84de7dc71f6394a32097e641ce8

    ii. Thêm Library và Resource.

    8b4e0e02719a6413c7e314dbcdeb60310da347ee

    Bước 5. Tạo Testcase.

    • i. Nhấp phải chọn testsuite vừa tạo, chọn “New Test Case”. Sau đó điền tên Test case.
    e6ef5e416f1ca33cef7966fd0b5427c22a330394
    284310247cf64ebe4c844d291836fc06eb897a5f
    • ii. Viết script cho testcase.
    0bdb909df2a546a4ac483f0de1554d7125f914db

    Bước 6. Thực thi testsuite.

    d57e08d43866db4c2b1f0e55a629f8a0d4363ff4

    Bước 7. Kiểm tra kết quả.

    e7f0ef58678c18a5b97a60c8acd846d5db588259
    f8da3fd9b4bf31c6e5d335ef0a9da96f099c8370
    9dbf74f16b6f43d9f5f288b6a647d3bb3ec9b686

    V. Tham khảo

    Techtalk via Viblo

  • iOS/Auto Layout – Phần 3: Anatomy of a Constraint, cách để tạo constraints bằng code

    iOS/Auto Layout – Phần 3: Anatomy of a Constraint, cách để tạo constraints bằng code

    Lời mở đầu

    Bài viết này của mình có 2 nội dung chính. Đầu tiên mình sẽ nói về cấu tạo của một constraint để các bạn có thể hình dung được nó hoạt động như thế nào. Thứ hai mình sẽ nói về việc làm thế nào để tạo một constraint sử dụng code mà không cần dùng đến Interface buider.



    Anatomy of a Constraint

    Layout của view hierarchy được định nghĩa là một chuỗi các phương trình tuyến tính. Mỗi ràng buộc đại diện cho một phương trình duy nhất. Mục tiêu của bạn là khai báo một loạt các phương trình có một và chỉ một giải pháp khả thi.

    Giờ chúng ta sẽ xem cấu tạo của một constraint nó như nào nhé:

    Constraint này nói rằng cạnh trái của view màu đỏ(Red view’ leading edge) phải là 8 points sau cạnh phải của view màu xanh(Blue view’s trailing edge). Phương trình của nó có các phần như sau:

    • Item 1: Đây là phần đầu tiên trong phương trình, trong trường hợp này nó là View màu đỏ. Phần này phải là một View hoặc là một layout guide.
    • Attribute 1: Là thuộc tính được constraint trên Item 1. Trường hợp này nó là cạnh trái của Red View(Red view’s leading edge).
    • Relationship: Mối quan hệ giữa bên trái và bên phải của phương trình. Giá trị của relationship có thể thuộc 1 trong 3 giá trị: bằng, lớn hơn hoặc nhỏ hơn. Trong trường hợp này bên trái và bên phải bẳng nhau.
    • Multiplier: Hệ số giá trị của Attribute được nhân với số này. Trong trường hợp này hệ số nhân là 1.0. Trong các trường hợp mặc định hệ số này là 1.0, nên ta có thể bỏ đi nếu không muốn thay đổi giá trị này.
    • Item 2: Là phần thứ 2 trong phương trình. Trong trường hợp này nó là Blue View. Không giống với Item 1, nó có thể để trống.
    • Attribute 2: Là thuộc tính được constraint trên Item 2. Trong trường hợp này là cạnh phải của BlueView. Nếu Item 2 để trống thì Attribute 2 sẽ không tồn tại
    • Constant: Một hằng số bù trừ, trong trường hợp này nó là 8.0. Giá trị này được thêm vào giá trị của thuộc tính 2(Value of Attribute 2).

    Hầu hết các constraint xác định một mối quan hệ giữa hai items trong giao diện người dùng. Những constraint này có thể đại diện cho các View hoặc Layout guide. Các Constraint cũng có thể xác đinh mối quan hệ giữa hai thuộc tính khác nhau của một item.
    Ví dụ: bạn có thể đặt Aspect ratio giữa chiều cao của 1 item với chiều rộng của nó. Bạn cũng có thể gán giá trị hằng số cho chiều cao hoặc chiều rộng của Item đó. Khi làm việc với giá trị Constant, Item 2 được để trống thì attribute 2 sẽ được gán là không phải thuộc tính và multiplier được gán là 0.0

    Auto Layout Attributes

    Trong Auto Layout, các thuộc tính được xác định là một tính năng có thể được ràng buộc(Constraint). Thông thường, nó bao gồm 4 cạnh: Trên(Top), Dưới(bottom), Trái(leading), Phải(trailing), cũng như chiều cai, chiều rộng và căn giữa theo chiều dọc và ngang. Các text cũng có một hoặc nhiều thuộc tính baseline.

    Các bạn có thể xem thêm NSLayoutAttribute enum.

    Cách tạo constraint bằng code

    Auto layout constraints cho phép chúng ta tạo ra các view tự động điều chỉnh theo các Size Class và Vị trí khác nhau. Các constraint sẽ đảm bảo view của bạn điều chỉnh phù hợp với bất kỳ thay đổi kích thước nào mà không phải cập nhật thủ công các khung hoặc vị trí của nó.

    Tuy nhiên, ngoài việc sử dụng Interface Builder để Auto Layout thì chúng ta có thể sử dụng code để Auto Layout.

    Việc viết AutoLayout có một số ưu điểm và nhược điểm như sau:
    Ưu điểm:
    – Dễ dàng merge code khi dùng Git
    – Dễ debug
    – Các constraint có thể dễ nhìn hơn
    Nhược điểm:
    – Không có đại diện trực quan. Việc này khiến bạn phải tưởng tượng thay vì như IB cho ta nhìn thấy Views của mình trên màn hình ở nhiều kích thước khác nhau.
    – Bạn có thể sẽ phải viết nhiều code layout lên trên ViewController của mình.

    Theo mình để có thể làm tốt Auto Layout bằng code thì mình nghĩ các bạn nên sử dụng thành thạo Interface Builder trước.
    Việc kết hợp giữa Interface Builder và constraint trong code cũng có thể là một giải pháp tốt. Tuy nhiên, hãy luôn nhớ rằng việc đó sẽ khiến nó trở nên khó hiểu hơn.

    Tạo constraints bằng cách sử dụng Layout Anchors

    Việc đầu tiên chúng ta cần làm là phải set thuộc tính translatesAutoresizingMaskIntoConstraints thành false. Việc này nhằm mục đích ngăn các view’s auto-resizing mask được chuyển sang Auto Layout constraints và ảnh hưởng đến các constraint của bạn.

    Tiếp theo chúng ta sẽ bắt đầu tạo một mảng chứa các constraints. Trong mảng này chúng ta sẽ định nghĩa các constraints mà bạn muốn gắn nó cho một View nào đó.

    Trong trường hợp này mình muốn tạo ra 1 view màu đỏ có width = 200, height = 200 và căn giữa màn hình. Vì vậy mình sẽ tạo ra một mảng constraint như dưới đây.

    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            let myView = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
            myView.backgroundColor = .red
            view.addSubview(myView)
            myView.translatesAutoresizingMaskIntoConstraints = false
            let constraints = [
                myView.centerYAnchor.constraint(equalTo: view!.centerYAnchor),
                myView.centerXAnchor.constraint(equalTo: view!.centerXAnchor),
                myView.widthAnchor.constraint(equalToConstant: 200.0),
                myView.heightAnchor.constraint(equalToConstant: 200.0)
            ]
    
            NSLayoutConstraint.activate(constraints)
        }

    Đó là những dòng code cơ bản để có thể tạo constraint cho 1 view bằng code. Và nó khá dễ đọc và dễ hiểu.
    Dòng cuối cùng của đoạn code trên nhằm mục đích active một chuỗi các constraint mà bạn tạo ra.

    Kết quả chúng ta thu được sẽ như hình dưới đây:

    UIView cung cấp cho chúng ta 1 tập hợp các thuộc tính neo cho phép bạn thiết lập các mối quan hệ giữa các View với nhau.
    Dưới đây là danh sách các thuộc tính neo:

    extension UIView {
    
        /* Constraint creation conveniences. See NSLayoutAnchor.h for details.
         */
        @available(iOS 9.0, *)
        open var leadingAnchor: NSLayoutXAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var trailingAnchor: NSLayoutXAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var leftAnchor: NSLayoutXAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var rightAnchor: NSLayoutXAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var topAnchor: NSLayoutYAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var bottomAnchor: NSLayoutYAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var widthAnchor: NSLayoutDimension { get }
    
        @available(iOS 9.0, *)
        open var heightAnchor: NSLayoutDimension { get }
    
        @available(iOS 9.0, *)
        open var centerXAnchor: NSLayoutXAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var centerYAnchor: NSLayoutYAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var firstBaselineAnchor: NSLayoutYAxisAnchor { get }
    
        @available(iOS 9.0, *)
        open var lastBaselineAnchor: NSLayoutYAxisAnchor { get }
    }

    Với mỗi Anchor nó trả về các subclass từ NSLayoutAnchor đi kèm với một số phương thức phổ biến để thiết lập mối quan hệ. Nó bao gồm =, >, <, >=, <=. Cũng giống như khi chúng ta dùng Interface Builder. Đây là tài liệu để làm quen về nó Documents.

    NOTE: Như các bạn đã thấy, nó chỉ hỗ trợ từ iOS 9, Hầu hết các ứng dụng bây giờ đều sẽ hỗ trợ từ iOS 9 trở lên, vì vậy chúng ta không cần phải lo lắng quá nhiều về nó.

    Order of constraints

    Khi các bạn bắt đầu viết các constraint của mình, điều quan trọng là bạn phải nhớ thứ tự của các constraint của bạn khi bạn làm việc với các constants.

    let innerView = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
    let outerView = UIView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
    innerView.backgroundColor = .red
    outerView.backgroundColor = .blue
    
    view.addSubview(outerView)
    outerView.addSubview(innerView)
    innerView.translatesAutoresizingMaskIntoConstraints = false
    outerView.translatesAutoresizingMaskIntoConstraints = false
    let constraintsOuter = [
        outerView.centerYAnchor.constraint(equalTo: view!.centerYAnchor),
        outerView.centerXAnchor.constraint(equalTo: view!.centerXAnchor),
        outerView.widthAnchor.constraint(equalToConstant: 200.0),
        outerView.heightAnchor.constraint(equalToConstant: 200.0)
    ]
    
    NSLayoutConstraint.activate(constraintsOuter)
    
    let constraints = [
        innerView.topAnchor.constraint(equalTo: outerView.topAnchor),
        innerView.leftAnchor.constraint(equalTo: outerView.leftAnchor, constant: 40),
        innerView.bottomAnchor.constraint(equalTo: outerView.bottomAnchor),
        innerView.rightAnchor.constraint(equalTo: outerView.rightAnchor, constant: -40)
    ]
    
    NSLayoutConstraint.activate(constraints)

    Kết quả như hình dưới đây:

    Mục đích của đoạn code này là tạo ra mảng constraint cho innerView sao cho top của nó bằng top của của outerView, bot = bot của outerView, và cách đều 2 bên = 40 points.
    let constraints = [
    innerView.topAnchor.constraint(equalTo: outerView.topAnchor),
    innerView.leftAnchor.constraint(equalTo: outerView.leftAnchor, constant: 40),
    innerView.bottomAnchor.constraint(equalTo: outerView.bottomAnchor),
    innerView.rightAnchor.constraint(equalTo: outerView.rightAnchor, constant: -40)
    ]
    Ở dòng được highlight kia giá trị của constant phải = -40. Vì lúc này vị trí của outerView đã được cố định, và outerView.rightAnchor lúc này được coi là gốc(Ox) của trục tọa độ. Vậy nên vị trí bên trái của gốc tọa độ Ox ta hiểu là giá trị âm và ngược lại.
    Ta có thể đổi ngược lại vị trí của innerView và outerView để được kết quả như trên hình theo đoạn có dưới đây:( Không nên viết theo kiểu này mà nên viết theo kiểu trên để dễ nhìn hơn)
    outerView.rightAnchor.constraint(equalTo: innerView.rightAnchor, constant: 40)

    Tương tự đối với top và bottom là trục Oy hướng xuống dưới, phía trên của gốc Oy là giá trị âm và ngược lại.

    NOTE: Khi các bạn thêm các constraint bằng code, các bạn nên làm theo thứ tự để code của ta dễ kiểm soát và dễ hiểu hơn.

    Một số Layout guides hay sử dụng

    UIVew cũng có một vài Layout guides có thể được sử dụng làm neo như sau:

    • layoutMarginGuide: Đặt các constraint và giữ lề của layout bằng 1 khoảng trống cố định (16 points).
    • readableContentGuide: Constraints chiều rộng của view giúp người dùng dễ đọc.
    • safeAreaLayoutGuide: Đặt các constraint giúp view của bạn không bị che bởi các thanh và nội dung khác.

    Đây là đoạn code mẫu sử dụng Layout guide(safeAreaLayoutGuide)

    let constraints = [
        outerSquare.topAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.topAnchor),
        outerSquare.leftAnchor.constraint(equalTo: viewController.view.safeAreaLayoutGuide.leftAnchor),
        outerSquare.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
        outerSquare.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor)
    ]

    Hỗ trợ ngôn ngữ từ Phải qua trái

    Có vẻ rất rõ ràng khi bạn sử dụng leftAnchor và rightAnchor, nhưng bạn sẽ phải nghĩ về việc sử dụng leadingAnchor và trailingAnchor để thay thế. Nhằm mục đích hỗ trợ các ngôn ngữ từ phải qua trái. Điều này rất quan trọng khi sử dụng views như là Labels trong trường hợp bạn muốn chúng được lật lại cho các ngôn ngữ từ phải sang trái.



    Phần kết

    Mình hi vọng bài viết này giúp các bạn có thể tạo các constraints bằng code sẽ dễ dàng hơn.
    Nó sẽ là một giải pháp thay thế tuyệt vời để thiết lâp các constraints khi bạn không muốn dùng Interface Builder.

  • [Docker] Docker vs vagrant, tôi đã quản lý container như thế nào?

    [Docker] Docker vs vagrant, tôi đã quản lý container như thế nào?

    Bạn có gặp khó khăn trong việc xây dựng môi trường phát triển ứng dụng không? Mỗi khi bắt đầu một dự án mới, việc cài đặt môi trường phát triển thường tốn khá nhiều thời gian. Do đó việc xây dựng và chia sẻ môi trường phát triển giữa các thành viên trong dự án là thực sự cần thiết. Trong bài viết này tôi sẽ chia sẻ với các bạn cách mà tôi đã làm với các dự án của mình.

    Tại sao lại sử dụng docker?

    Chia sẻ qua một chút về quá trình trước khi tôi sử dụng docker trong các dự án của mình. Năm 2014, tôi bắt đầu xây dựng môi trường phát triển cho dự án sử dụng vargrant. Với vargrant tôi đã có thể đóng gói các dịch vụ được sử dụng trong dự án và chia sẻ với các thành viên trong dự án. Nhưng bạn biết không dự án có sử dụng PostgreSQL và Couchbase, khi đó tôi đã tạo 2 máy ảo vagrant cho các dịch vụ này. Bạn biết đấy, vargrant sẽ tạo ra một máy ảo hoàn chỉnh và sau đó tôi cài các dịch vụ tôi cần sử dụng lên đó. Nó thực sự là vấn đề với chiếc PC của tôi :). Ngoài ra bạn sẽ gặp khó khăn trong việc kết nối các máy ảo này với nhau nữa, bạn cần setting sao cho chúng ở cùng một mạng riêng.

    Khi sử dụng docker thì sao? Với mỗi dịch vụ bạn tạo ra một container riêng giống như một máy ảo vargrant tôi đã tạo ở trên. Nhưng điểm khác biệt là gì?

    • Với docker bạn không cần lo lắng về vấn đề cài dịch vụ nữa. Nó đã tự động làm việc đó rồi.

    • docker không tạo ra một máy ảo hoàn chỉnh với các dịch vụ thừa trong đó. Nó đơn giản tạo ra một môi trường đủ để bạn chạy dịch vụ của mình.

    • Việc cấu hình mạng riêng và chuyển tiếp cổng vào container cũng được được thiết lập dễ dàng hơn.

    Với chừng đó lý do là đủ để tôi chuyến sang sử dụng docker rồi 🙂

    Tôi đã quản lý container bằng docker như thế nào?

    Để quản lý container tôi sử dụng Docker Compose, công cụ này có sẵn khi bạn cài Docker Desktop

    Cấu hình container

    • Sử dụng docker-compose.yml để cấu hình các dịch vụ bạn muốn sử dụng trong ứng dụng

    Trong phạm vi bài viết này tôi sẽ cấu hình để tạo ra hai container cho các dịch vụ MySQL và DynamoDB.

    docker-compose.yml

    version: '3'
    services:
      mysql:
        image: mysql:5.7.26
        # set hostname để bạn có thể access vào container bằng tên này
        container_name: mysql
        ports:
          # Cấu hình forward port từ host vào docker container
          - '3306:3306'
        volumes:
          # Cấu hình thư mục chưa schema bạn muốn import vào MySQL
          - ./mysql/initdb.d:/docker-entrypoint-initdb.d
          # mount thư mục MySQL data để có thể backup dữ liệu nếu cần
          - ./mysql/data:/var/lib/mysql
          # Các cấu hình bạn cần thay đổi cho dịch vụ MySQL
          - ./mysql/conf.d/my.cnf:/etc/mysql/my.cnf
          # mount thư mục log để trace lỗi nếu cần
          - ./mysql/log:/var/log/mysql
        # các biến môi trường sử dụng qua tham số -e khi run container hoặc cấu hình trong .env như bên dưới
        environment:
          # set mật khẩu cho tài khoản root
          - MYSQL_ROOT_PASSWORD=$DB_ROOT_PASSWORD
          # tên database bạn muốn tạo sau khi container được khởi động
          - MYSQL_DATABASE=$DB_DATABASE
          # tạo thêm một user mới với tên được cấu hình trong $MYSQL_USER
          - MYSQL_USER=$DB_USER
          # set mật khẩu cho user được tạo ở trên
          - MYSQL_PASSWORD=$DB_PASSWORD
    
      dynamodb:
        image: amazon/dynamodb-local
        # set hostname để bạn có thể access vào container bằng tên này
        container_name: dynamodb
        ports:
          # Cấu hình forward port từ host vào docker container
          - '8000:8000'
        volumes:
          # mount thử mục data của DynamoDB để có thể backup
          - ./dynamodb/data:/home/dynamodblocal/data
        entrypoint: java
        command: '-jar DynamoDBLocal.jar -sharedDb -dbPath /home/dynamodblocal/data'
    
    • Set các biến môi trường bằng .env

    Tại thư mục chưa docker-compose.yml bạn tạo .env như sau:

    .env

    DB_ROOT_PASSWORD=hieunv@123456
    DB_DATABASE=test
    DB_USER=hieunv
    DB_PASSWORD=hieunv@123456
    

    Khởi động các dịch vụ

    Bạn sử dụng docker-compose để khởi động các dịch vụ như đã cấu hình ở trên

    docker-compose up -d
    
    Tạo container bằng docker-compose

    Xoá các container

    Khi bạn không muốn sử dụng container nữa hoặc khi có lỗi container mà bạn muốn tạo lại thì có thể xoá nhanh các container đã tạo bằng lệnh sau:

    docker-compose down -v
    

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng sau bài viết này các bạn sẽ sử dụng Docker trong dự án của mình.