Blog

  • APPIUM Tutorial cho kiểm thử Android & iOS Mobile App

    Appium là gì?

    APPIUM là framework mã nguồn mở để kiểm thử UI cho các ứng dụng di động. Appium cho phép thử nghiệm ứng dụng native, hibrid và ứng dụng web, hỗ trợ test automation trên các thiết bị cũng như cả emulator hoặc simulator. Nó còn hỗ trợ kiểm thử cross-platform , tức là API đơn hoạt động cho kiểm thử Android và iOS.

    APPIUM KHÔNG phụ thuộc vào hệ điều hành thiết bị di động. Bởi vì APPIUM có khung hoặc trình bao bọc dịch các lệnh Selenium WebSearch thành các lệnh UIAutomation (iOS) hoặc UIAutomator (Android) tùy thuộc vào loại thiết bị, hệ điều hành nào.

    Appium hỗ trợ tất cả các ngôn ngữ có Selenium client librarie như- Java, Objective-C, JavaScript với node.js, PHP, Ruby, Python, C #, v.v.

    Trong hướng dẫn này, chúng ta sẽ tìm hiểu về:

    • APPIUM hoạt động như thế nào?
    • Điều kiện tiên quyết để sử dụng APPIUM
    • Cài đặt máy tính để bàn Appium:
    • Appian Inspector
    • Đính kèm Trình giả lập Android với Appium
    • Trường hợp kiểm tra APPIUM cho ứng dụng Android gốc (Máy tính)
    • Hạn chế sử dụng APPI
    • Các lỗi thường gặp và các bước khắc phục sự cố trong Appium

    APPIUM hoạt động như thế nào?

    • Appium là một ‘HTTP Server’ được viết bằng nền tảng Node.js và điều khiển iOS và phiên Android sử dụng giao thức dây JSON của Webdo. Do đó, trước khi khởi chạy Máy chủ Appium, Node.js phải được cài đặt sẵn trên hệ thống.
    • Khi Appium được tải xuống và cài đặt, thì máy chủ sẽ được thiết lập trên máy của chúng tôi hiển thị API REST.
    • Nó nhận được kết nối và yêu cầu lệnh từ máy khách và thực hiện lệnh đó trên thiết bị di động (Android / iOS).
    • Nó phản hồi lại với các phản hồi HTTP. Một lần nữa, để thực hiện yêu cầu này, nó sử dụng các khung tự động kiểm tra di động để điều khiển giao diện người dùng của các ứng dụng. Một khung như: –
    • Apple Cụ cho iOS (Dụng cụ chỉ khả dụng trong Xcode 3.0 trở lên với OS X v10.5 trở lên)
    • Google UIAutomator cho Android API cấp 16 trở lên
    • Selendroid cho API Android cấp 15 trở xuống

    Điều kiện tiên quyết để sử dụng APPIUM

    1. Cài đặt ANDROID SDK (Studio)
    2. Cài đặt JDK (Bộ phát triển Java)
    3. Cài đặt Eclipse
    4. Cài đặt TestNg cho Eclipse
    5. Cài đặt máy chủ Selenium
    6. Appium Client Library
    7. APK App Info on Google Play
    8. js (Không bắt buộc – Bất cứ khi nào máy chủ Appium được cài đặt, theo mặc định, nó đi kèm với “Node.exe” & NPM. Nó được bao gồm trong phiên bản hiện tại của Appium.)
    9. Install Appium Desktop

    Cài đặt Appium Desktop: Appium Studio là một ứng dụng GUI nguồn mở để cài đặt Máy chủ Appium. Nó đi kèm với tất cả các điều kiện tiên quyết để cài đặt và sử dụng Máy chủ Appium. Nó cũng có Thanh tra để nhận thông tin cơ bản trên Ứng dụng của bạn. Nó đi kèm với một Trình ghi để tạo mã soạn sẵn để tự động hóa các ứng dụng di động của bạn.

    Bước 1)

    Truy cập http://appium.io/ và nhấp vào Tải xuống Appium.

    Bước 2)

    Đối với Windows, chọn tệp exe và tải xuống. Tệp khoảng 162 MB sẽ mất thời gian để tải xuống dựa trên tốc độ internet của bạn.

    Bước 3)

    Nhấp vào exe đã tải xuống.

    Bước 4)

    Trên máy Windows, không cần cài đặt Appium. Nó chạy trực tiếp từ exe. Khi bạn nhấp vào exe, bạn sẽ thấy hình ảnh sau đây trong vài phút.

    Đối với Mac, bạn cần cài đặt dmg

    Bước 5)

    Tiếp theo bạn sẽ thấy Cửa sổ khởi động máy chủ. Nó điền vào tùy chọn máy chủ và cổng mặc định mà bạn có thể thay đổi. Nó cũng đề cập đến phiên bản của Appium đang được sử dụng.

    Bước 6)

    Khi nhấp vào nút Máy chủ Bắt đầu, một máy chủ mới được khởi chạy trên máy chủ và cổng được chỉ định. Đầu ra nhật ký máy chủ được hiển thị.

    Bước 7)

    Nhấp vào Cửa sổ Phiên mới .

    Bước 8)

    Bạn có thể nhập Khả năng mong muốn và bắt đầu một phiên.

    Appian Inspector

    Tương tự như công cụ ghi và phát lại Selenium IDE, Appium có ‘Inspector’ để ghi và phát lại. Nó ghi lại và phát hành vi ứng dụng gốc bằng cách kiểm tra DOM và tạo các tập lệnh thử nghiệm bằng bất kỳ ngôn ngữ nào bạn muốn. Tuy nhiên, hiện tại, không có hỗ trợ cho Thanh tra Appium cho Microsoft Windows. Trong Windows, nó khởi chạy Appium Server nhưng không kiểm tra các phần tử. Tuy nhiên, trình xem UIAutomator có thể được sử dụng như một tùy chọn để Kiểm tra các phần tử.

    Các bước để bắt đầu với Appium Inspector trên máy Mac: –

    Bước 1)

    Tải xuống và khởi động máy chủ Appium của bạn với Địa chỉ IP mặc định 0.0.0.0 và cổng 4725.

    Chọn tệp nguồn hoặc tệp .app từ cục bộ để kiểm tra. Kiểm tra hộp kiểm ‘Đường dẫn ứng dụng’ để bật nút ‘Chọn’.

    Bước 2)

    Bây giờ, bấm vào nút ‘Chọn’ sẽ cung cấp tùy chọn để duyệt và chọn tệp kiểm tra từ ổ đĩa cục bộ.

    Bước 3)

    Khởi động Trình mô phỏng trên máy Mac.

    Bước 4)

    Nhấp vào nút ‘Khởi chạy’ từ góc trên bên phải, cho phép biểu tượng màu xanh lam. Một lần nữa, nhấp vào biểu tượng màu xanh này, nó sẽ mở Trình kiểm tra và Trình mô phỏng Appium với một ứng dụng được chọn trước.

    Bước 5)

    • Khởi chạy Trình kiểm tra Appium của bạn sẽ hiển thị phân cấp thành phần trong cấu trúc theo cột. Ngoài ra, người dùng có thể áp dụng các hành động bằng các nút như Tap, Swipe, v.v.

    Bước 6)

    Nhấp vào nút ‘Dừng’ để dừng ghi âm.

    Attach Android Emulator cho Appium

    Bước 1)

    Cài đặt SDK Android trong hệ thống của bạn.

    Chuyển đến Bảng điều khiển >> Hệ thống và Bảo mật >> Hệ thống và từ bảng điều khiển bên trái, nhấp vào Settings Cài đặt hệ thống nâng cao ‘. Từ ‘Thuộc tính hệ thống’ bật lên, nhấp vào tab ‘Nâng cao’ và sau đó nhấp vào nút “Biến môi trường”.

    Bước 2)

    Bây giờ, từ ‘Biến môi trường’ bật lên, ‘nhấp đúp chuột vào’ Đường dẫn ‘và đặt biến ANDROID_HOME trỏ đến thư mục SDK của bạn. Trong đường dẫn nối thêm toàn bộ đường dẫn thư mục SDK.

    ví dụ: C: \ Người dùng \ ABC \ Máy tính để bàn \ adt-bundled-windows-x86_64-20140321 \ sdk

    Bước 3)

    Khởi động trình giả lập Android hoặc bất kỳ thiết bị Android nào gắn vào hệ thống của bạn (Đảm bảo bạn đã bật tùy chọn Gỡ lỗi Android trong thiết bị Android của mình. Để kiểm tra Tùy chọn gỡ lỗi. Đi đến Cài đặt thiết bị >> Tùy chọn nhà phát triển >> Kiểm tra “Tùy chọn gỡ lỗi” ).

    Bước 4)

    Mở Dấu nhắc lệnh và điều hướng đến thư mục \ platform-tools \ của SDK Android (Ví dụ: D: \ adt-bundle-windows-x86_64-20130514 \ sdk \ platform-tools).

    Bước 5)

    • Chạy lệnh ‘adb thiết bị’. Bạn có thể thấy thiết bị được kết nối của mình được liệt kê trong cửa sổ Dấu nhắc Lệnh. (Trong CMD ghi ‘> thiết bị adb’- Lệnh này sẽ liệt kê các phiên bản trình giả lập được kết nối. Ví dụ: adb hès giả lập-5554 cài đặt <Vị trí của tệp .apk>)

    Bước 6)

    • Chạy lệnh ‘adb start-server’. Nó sẽ khởi động máy chủ ADB sẽ được Appium sử dụng để gửi lệnh đến thiết bị Android của bạn.

    Bước 7)

    Bây giờ, điều hướng đến thư mục Appium trong hệ thống của bạn và bắt đầu Appium bằng cách nhấp vào tệp Appium.exe.

    Bước 8)

    Không thay đổi địa chỉ IP hoặc số cổng và nhấp vào nút ‘Khởi chạy’. Bảng điều khiển Appium của bạn bắt đầu từ 127.0.0.1:4723 như hiển thị bên dưới.

    Bước 9)

    Nhấp vào nút ‘Bắt ​​đầu’, máy chủ Appium bắt đầu chạy trên hệ thống của bạn.

    Trường hợp kiểm tra APPIUM cho ứng dụng Android gốc (Máy tính)

    Bước 1)

    Tải xuống plugin nhật thực ADT hoặc tải xuống gói ADT riêng tại đây

    Bước 2)

    Mở Eclipse và tạo một dự án mới >> Gói >> Lớp

    Bước 3)

    Nhập thư viện Selenium và Testng bên trong dự án mới đó.

    Bước 4)

    Bây giờ Tạo Chương trình thử nghiệm nhỏ cho ‘Calculator.app’ để tổng hai số.

    gói src_Appium; 
    nhập java.net.MalformedURLException; 
    nhập java.net.URL; 
    nhập org.openqa.selenium.By; 
    nhập org.openqa.selenium.WebDriver; 
    nhập org.openqa.selenium.WebEuity; 
    // nhập org.openqa.selenium.remote.CapabilityType; 
    nhập org.openqa.selenium.remote.DesiredCapabilities; 
    nhập org.openqa.selenium.remote.RemoteWebDriver; 
    nhập org.testng.annotations. *; 
    
    
    Máy tính lớp công khai { 
    Trình điều khiển WebDriver; 
    
    @B BeforeClass 
    void void setUp () ném MalformedURLException { 
    	// Thiết lập các khả năng mong muốn và chuyển gói hoạt động ứng dụng và gói ứng dụng Android cho Appium 
    	DesiredCapabilities ability = new DesiredCapabilities ();
    	ability.setCapability ("BROWSER_NAME", "Android"); 
    	ability.setCapability ("VERSION", "4.4.2"); 
    	ability.setCapability ("deviceName", "Trình giả lập"); 
    	ability.setCapability ("platformName", "Android"); 
     
       
       ability.setCapability ("appPackage", "com.android.calculator2"); 
    // Tên gói này của ứng dụng của bạn (bạn có thể lấy nó từ ứng dụng thông tin apk) 
    	ability.setCapability ("appActivity", "com.android.calculator2.Calculator"); 
       trình điều khiển = new RemoteWebDriver (URL mới ("http://127.0.0.1:4723/wd / hub "), khả năng);
    
       // định vị Văn bản trên máy tính bằng cách sử dụng By.name () 
       WebEuity Two = driver.findEuity (By.name ("2")); 
       hai.click (); 
       WebEuity plus = driver.findEuity (By.name ("+")); 
       dấu cộng.click (); 
       WebEuity bốn = driver.findE bổ sung (By.name ("4")); 
       bốn.click (); 
       WebEuity bằngTo = driver.findE bổ sung (By.name ("=")); 
       bằngTo.click (); 
       // xác định vị trí hộp chỉnh sửa của máy tính bằng cách sử dụng By.tagName () 
       WebEuity results = driver.findEuity (By.tagName ("EditText")); 
    	// Kiểm tra giá trị tính toán trên hộp chỉnh sửa
    khẳng định kết quả.getText (). bằng ("6"): "Giá trị thực tế là:" + results.getText () + "không khớp với giá trị mong đợi: 6"; 
    
    } 
    
    @AfterClass 
    công khai void void () { 
    	// đóng trình điều khiển ứng 
    	dụng.quito (); 
    } 
    }
    

    Những mặt hạn chế của APPI

    Appium không hỗ trợ thử nghiệm Phiên bản Android thấp hơn 4.2 Hỗ trợ hạn chế để thử nghiệm ứng dụng lai. ví dụ: không thể kiểm tra hành động chuyển đổi của ứng dụng từ ứng dụng web sang nguồn gốc và ngược lại. Không hỗ trợ để chạy Appium Inspector trên Microsoft Windows.

    Nguồn: https://www.guru99.com/introduction-to-appium.html

  • [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    [React] Class Component và Function Component, bạn chọn viết theo cách nào?

    Trong thế giới React thì chắc hẳn ai cũng biết đến Class Component và Function Component. Tuy nhiên có thể có những hiểu nhầm về hai loại component này. Trong bài viết này tôi sẽ thử so sánh hai cách viết này để giúp bạn có thể lựa chọn viết theo cách nào. Chúng ta cùng bắt đầu nhé.

    Cú pháp

    Khác nhau đầu tiên giữa Class ComponentFunction Component thể hiện ngay ở cách khai báo.

    Class Component

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      // phương pháp này bắt buộc phải khai báo hàm để kết xuất mã HTML
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Cách khai báo này khá quen thuộc với các bạn có nền tảng lập trình hướng đối tượng (OOP). Với những bạn mới học React hoặc chuyển sang học React thì phương pháp tiếp cận này có vẻ phù hợp và dễ hiểu.

    Function Component

    import React from 'react';
    
    export function TestComponent() {
      // phương pháp xem kết xuất mã HTML như là giá trị trả về của hàm
      return <div>TestComponent</div>;
    }
    

    Function component sử dụng cách tiếp cận khác đó là sử dụng pure function để khai báo component. Ban đầu function component được sử dụng để viết các component chỉ với mục đích kết xuất HTML mà thôi. Với các component với theo hướng tiếp cận này thì bạn sẽ không can thiệp được vào lifecycle của component. Do đó nó thướng được biết đến với tên gọi Stateless Component.

    Props

    Class Component

    props trong Class Component được xem như giá trị truyển vào cho hàm khởi tạo class.

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props); // bắt buộc phải có dòng này để gọi hàm khởi tạo của class cha nhé
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Function Component

    props trong Function Component thì được xem như là giá trị truyền vào hàm pure function khi định nghĩa component.

    import React from 'react';
    
    export function TestComponent(props) {
      return <div>TestComponent</div>;
    }
    

    Định nghĩa defaultPropspropTypes thì không có sự khác biệt giữa Class Component và Function Component.

    TestComponent.defaultProps = {};
    
    TestComponent.propTypes = {};
    

    State

    Trước khi React Hooks ra đời thì như đã nói ở trên Function Component con được biết đến với tên gọi Stateless Component. Nghĩa là nó không có state. Khi React Hooks ra đời thì Function Component cũng có state của riêng nó.

    Class Component

    state trong Class Component dược định nghĩa như sau:

    import React, { Component } from 'react';
    
    class TestComponent extends Components {
      constructor(props) {
        super(props);
        // khởi tạo giá trị state
        this.state = { isLoading: false };
      }
    
      render() {
        return <div>TestComponent</div>;
      }
    }
    

    Khi muốn thay đổi giá trị state bạn gọi phương thức setState của component:

    this.setState((state) => ({ isLoading: true }));
    

    Function Component

    state trong Function Component được định nghĩa như sau:

    import React, { useState } from 'react';
    
    export function TestComponent(props) {
      // giá trị khởi tạo state được truyền vào trong useState hook
      const [state, setState] = useState({ isLoading: false });
    
      return <div>TestComponent</div>;
    }
    

    Các bạn để ý hàm useState trả về giá trị của component state trong biến state và hàm setState. Khi muốn thay đổi giá trị của state thì bạn có thể gọi hàm setState.

    setState({ isLoading: true });
    

    Component Lifecycle

    Với Class component các bạn sẽ thấy component lifecycle khá rõ ràng với các hàm như componentDidMount, componentDidUpdate. Function Component thì không như vậy, toàn bộ việc sử lý lifecycle đều thông qua useEffect hook.

    // componentDidMount
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, []);
    
    // componentDidUpdate
    useEffect(() => {
      return () => {}; // componentWillUnmount
    }, [state]);
    

    Như các bạn thấy thì componentDidMountcomponentDidUpdate không chỉ định rõ khi nào thì hàm được gọi. Việc gọi hàm thì tự chúng ta hiểu dựa theo lifecycle của React component thôi. Với Function Component và useEffect thì khác, bạn có thể thấy [][state] chị định rõ ràng đối tượng phụ thuộc mà khi chúng thay đổi thì hàm truyển vào useEffect sẽ được gọi. Có vẻ như nó lại tường mình hơn là các hàm lifecycle trong class Component.

    Sau sự có mặt của TypeScript thì có lẽ Class Component và React Hooks thì có lẽ bạn Class Component chiếm ưu thế tuyệt đối. Thời điểm đó anh em thi nhau viết Class Component với TypeScript và tôi cũng thế :). Ngay đến cả facebook cũng support TypeScript với create-react-app nữa cơ mà. Thế nhưng khi React Hooks xuất hiện thì có vẻ gió đã đổi chiều, việc xử lý state và lifecycle với hook có vẻ đơn giản hơn rất nhiều. Các bạn thì thấy thế nào. Bạn sẽ chọn cách nào với dự án của mình?

  • iOS/Auto Layout – Phần 2: Sử dụng “XCode/Interface Builder” sao cho hiệu quả?

    iOS/Auto Layout – Phần 2: Sử dụng “XCode/Interface Builder” sao cho hiệu quả?

    Lời mở đầu

    Như các bạn đã biết, để giỏi một kĩ năng nào đó trước hết chúng ta phải biết nó là gì và cách sử dụng của nó như thế nào. Và luyện tập nó nhiều sẽ giúp các bạn nâng cao kỹ năng đó.
    Bài viết này mình sẽ giới thiệu tới các bạn cách tương tác với Interface Builder và sử dụng chúng trong Auto layout sao cho hiệu quả. Hi vọng nó sẽ giúp ích cho mọi người :v



    1. Interface Builder

    Interface builder là một phần trong ứng dụng XCode, nó giúp các nhà phát triển phần mềm iOS xây dựng giao diện trên đó.

    2. Tương tác với Interface Builder

    Dưới đây là hình ảnh về giao diện của nó

    1.2 View as …

    Ở thanh điều khiển này bạn có thể xem giao diện khác nhau như

    • Các thiết bị khác nhau từ kích thước lớn nhất tới nhỏ nhât, từ iphone đến iPad
    • Các Interface Style khác nhau như Dark mode, light mode
    • Các orientation(Chiều xem màn hình) khác nhau
    Cái này để kiểm tra trước xem việc auto layout của các bạn đã theo ý muốn hay chưa.

    1.3 Auto layout bar

    • Update Frames: Nút này sẽ được hiển thị khi mà một hoặc nhiều View đang không nằm đúng vị trí so với constraints nó đang có. Khi bấm vào chúng ta sẽ được giúp các view đó trở về đúng vị trí so với constraints của nó đang có.
    • Align: Để tạo các constraint căn chỉnh giữa các view, chẳng hạn như căn giữa, trái, phải, trên dưới …
    • Add new constraints: Để tạo các các constraint thông thường như trên, dưới, trái, phải, chiều rộng, chiều cao và tỉ lệ (aspect ratio)
    • Resolve AutoAuto Layout Issues: Để giải quyết các vấn đề về auto layout như:
      – Update constraint constants: Để cập nhật giá trị mới cho constraint đó theo đúng vị trí mà nó đang nằm trên SupperView.
      – Add missing Constraints: tự động thêm các constraint còn thiếu (Không nên dùng vì rất dễ dẫn đến việc thêm các constraint không mong muốn.
      – Clear constraint: Bỏ hết constraint
    • Embed In: Tạo 1 view mới và đưa hết các view được chọn vào trong đó. Mà có thể sẽ không mất đi các constraint hiện tại của các view được chọn.

    1.3.1 Align

    Add new alignment constraints

    Ở đây chúng ta thấy giao diện thêm các constraints về căn chỉnh được hiển thị

    Vertical in Container và Horizontally in container

    Vertically in container: giúp tạo constraint của các View được chọn gắn với super view. Trong đó các connstraint được tạo sẽ giúp các view đó căn giữa theo chiều dọc với super view của nó. Ở đây chúng ta có thể thay đổi constant của nó(mặc định là 0)

    Horizontally in container: Tương tự như trên nhưng theo chiều ngang.

    Kết quả khi add 2 contraint này cho button
    First Baselines

    Khác với 2 thằng ở trên, các thằng còn lại cần phải có 1 thằng “A” đã xác đinh được vị trí của nó(đầy đủ contraint). Thằng “A” này làm nhiệm vụ là cái mỏ neo giúp cho các thằng khác bám vào.

    Thằng này dùng để làm cho nhiều View( UILabel, UIButton …) có text nằm trên cùng hàng, không bị lệch lẹo.
    Để enable tính năng này lên chúng ta cần phải chọn ýt nhất 2 Views.

    NOTE: Nó căn theo đáy của text trong các views

    Kết quả
    Vertical center

    Tạo constraints giúp các views căn giữa theo chiều dọc
    Lưu ý: Nó căn theo frame size nên nó sẽ căn theo các frame của các views được thêm constraint

    Để hiểu rõ hơn chúng ta theo dõi hình dưới đây

    Horizontal center

    Tương tự như Vertical center nhưng theo chiều ngang.

    Bottom edges

    Dùng khi cần gắn constraint cho viewA luôn nằm ở đáy của viewB.
    Mặc định sẽ là viewA.bottom to viewB.bottom

    Top edges

    Dùng khi cần gắn constraint cho viewA nằm ở trên đầu của viewB.
    Mặc định sẽ là viewA.top to viewB.top

    Trailing edges

    Dùng khi cần gắn contraint cho viewA cùng căn trái 1 khoảng bằng căn trái của viewB
    Măc định sẽ là viewA.trailing to viewB.trailing

    Leading edges

    Dùng khi cần gắn constraints cho viewA cùng căn phải 1 khoảng bằng với căn phải của viewB.
    Mặc định sẽ là viewA.leading to viewB.leading

    Tất cả các Label được constraint xoay quanh trụ là Button.

    1.3.2 Add new constraints

    Thường dùng để thêm các constraints cho view được chọn như: trên, dưới, trái, phải, chiều rộng, chiều cao, tỉ lệ. Phần này thì mọi người dùng nhiều rồi nên mình không nói nhiều về nó.
    NOTE: Thông thường khi chọn XCode sẽ gợi ý gắn constraint với view gần với các cạnh của nó nhất(không tính view đè lên nhau)
    Tuy nhiên chúng ta có thể thay đổi constraint tới view đích bằng cách chọn mũi tên như hình dưới.

    Ở hình này bạn có thể thay đổi view đích
    • width: tạo constraint độ rộng của view
    • height: tạo constraint độ cao của view
    • equal width: Tạo constraints cho các view có cùng độ rộng (Chọn 2 view trở lên)
    • equal height: Tạo constraints cho các view có cùng độ cao (chọn 2 view trở lên)
    • Aspect ratio: tạo constraint tỉ lệ. Thường sử dụng cho những view sử dụng hình ảnh.

    NOTE: Bạn có thế chọn nhiều view rồi set equal width, height để set cho tất cả các view được chọn có cùng size.

    1.3.3 Embed In

    Embed In View
    Dùng khi bạn muốn gom các view được chọn vào 1 view lớn, để quản lý layout đễ hơn. Tính năng này sẽ tự tạo 1 một view lớn chứa và cách các view bên trong với inset cố định, mặc định là 20 points

    Embed In View Without Inset
    Khác với tính năng Embed In View tính năng này sẽ tạo với Inset = 0, cách dùng như Embed In View

    Embed In ScrollView
    Tự động tạo 1 scrollview và đưa tất cả các view được chọn vào bên trong nó.

    Embed In Stack View
    Tạo ra 1 stack view và đưa các view được chọn vào trong nó. Thằng này thường được dùng khi bạn có nhiều View có cùng kích thước. Mình sẽ viết 1 bài đi sâu vào thằng này sau.

    1.4 Add constraints bằng Chuột và bàn phím.

    Bạn có thể add constraint bằng cách:
    – Sử dụng chuột phải để kéo ViewA constraint với ViewB.
    – Bấm giữ phím control + chuột trái để kéo ViewA constraint với ViewB
    Khi đó 1 popup sẽ hiển thị cho chúng ta thêm Constraint một cách nhanh chóng.

    Lúc này bạn có thể chọn 1 constraint mà bạn muốn hoặc giữ Shift để chọn tạo nhiều constraint.
    Giữ phím Option để nhìn thấy chỉ số và tạo constraint với chỉ số đó.

    1.5 Thanh Menu

    Đối với mỗi người sẽ có sở thích khác nhau khi chọn hiển thị cái gì trên màn hình Interface Builder.
    Nhưng theo mình các bạn nên chọn hiển thị các mục như mình đã chọn :D, đặc biệt có mục
    Bounds Rectangles: Nó tạo ra các border xung quanh các View giúp các bạn dễ hình dung hơn khi thêm constraints.

    Preview mode

    Giúp các bạn có thể nhìn thấy Layout của mình hoạt động như nào mà không cần phải build app. Tính năng này Cực Kỳ hữu dụng khi các bạn muốn test layout. Nó còn tiết kiệm thời gian khi bạn đang làm trong 1 dự án lớn vì thời gian build app nó khá lâu.

    Ở chế độ preview bạn có thế bấm vào Layout để chỉnh lại bố cục của chế độ xem trước
    Hiện tại có 2 lựa chọn: Bên phải và xem ở dưới.

    Assistant

    Tính năng này chia đôi màn hình và tự động hiển thị file swift đang được kết nối với nó.
    Để chuyển qua tính năng này chúng ta mở thanh menu lên và chọn Assistant
    Hoặc dùng tổ hợp phím Option + Control + Command + Enter để mở nhanh.

    1.6 Xem và thay đổi các constraint trên 1 view

    Tab Show the size inspector

    Bạn có thể chọn 1 view và xem được tất cả các constraint liên quan tới nó ở tab Show The Size Inspector bên trên góc trái màn hình.
    Nếu muốn chỉnh sửa nhanh constant, priority và multiplier của constraint đó thì hãy bấm vào nút edit ở mỗi constraint.

    Nếu muốn xem và chỉnh sửa 1 constraint cụ thể thì các bạn có thể dùng chuột chon constraint đó và sửa nó. Ở Mục này ta có thể sửa được nhiều thông tin hơn, ta có thể thay đổi được constraint với 1 vị trí khác. Thậm chí cả 1 view khác luôn. Cụ thể xem dưới hình.



    Tổng kết

    Hi vọng bài viết của mình giúp các bạn sử dụng Xcode/Interface Builder dễ dàng hơn.

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

  • Push Notification trên iOS Simulator

    Push Notification trên iOS Simulator

    Như các bạn đã biết, để dùng APNS (Apple Push Notification service) thì chúng ta cần phải có device thật. Nhưng chuyện đó đã là quá khứ khi ở bản 11.4 beta, Apple đã cho phép test push notification ngay trên simulator. Tuyệt vời !!! ?

    Để có thể push notification trên simulator, bạn cần:
    Bước 1: Tải Xcode 11.4 beta hoặc các phiên bản mới hơn tại link nè: https://developer.apple.com/download/

    Bước 2: Tạo project và grant permission 
    Appdelegate.swift, import framework UserNotifications, và yêu cầu quyền nhận notification ở hàm application(_:didFinishLaunchingWithOptions:)

    Bước 3: Tạo file APNS payload
    APNS payload là một file json dictionary chứa đựng các thông tin của Notification như kiểu thông báo, nội dung thông báo… Bạn có thể vào đây để xem thêm chi tiết:
    https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification

    Mình tạo file payload thêm 1 key “Simulator Target Bundle” như sau:

    Trong đó “yourBundleID” là bundleID app của bạn, bundle project của mình là “com.self.NotificationSimulator

    Bước 4:  Giờ kéo thả vào simulator thôi!
    Giờ bạn hãy kéo file payload vừa tạo vào simulator, xem điều kì diệu gì xảy ra nhé 

    Simulator đã có notification ?

    Ngoài cách kéo thả file APNS vào Simulator, ta còn có thể dùng câu lệnh Command để gửi noti. Ở Xcode 11.4 này đã có thêm command xcrun simctl push hỗ trợ việc bắn notification.

    xcrun simctl push <simulator-identifier> <path-to-payload-file>

    trong đó <simulator-identifier> là ID của simulator, <path-to-payload-file> là đường dẫn đến file payload. Bạn có thể lấy ID simulator như sau:

    Nếu bạn ngại việc copy identifier, bạn có thể dùng xcrun simctl push booted <path-to-payload-file> để push notification ngay trên simulator đang mở. Và kết quả:

    Kết luận

    Giờ đây ta có thể test push notification thật đơn giản trên simulator. Ta có 2 cách để test:
    – Kéo thả file APNS vào simulator
    – Trỏ đường dẫn file APNS hoặc Json payload qua command line
    Sau bài viết này, mình sẽ giới thiệu các bạn về Leanplum – một marketing platform cho mobile, và xem điểm giống và khác nhau giữa Leanplum vs Firebase nhé

    Nguồn: https://swiftsenpai.com/xcode/simulating-push-notifications-in-ios-simulator/

  • iOS: Slicing Image

    iOS: Slicing Image

    Nội dung bài viết:

    • Tổng quan
    • Cắt ảnh bằng Xcode Slicing Feature
    • Cắt ảnh bằng code.

    Tổng quan

    Trong 1 app iOS, sẽ có những buttons, views,… có kích thước khác nhau nhưng lại có cùng 1 background image.
    Nếu chỉ đơn giản dùng 1 background image cho các buttons, views,… đó thì bạn sẽ gặp phải tình trạng sau:

    Trong trường hợp này, việc sử dụng chung 1 image sẽ làm image bị co/dãn, làm xấu ảnh.
    Vậy nếu bạn chỉ muốn co/dãn phần giữa của ảnh, còn các phần ở 4 góc không bị co/dãn -> Hãy dùng slicing image.

    • Slicing image cho phép bạn có thể cắt 1 bức ảnh theo chiều dọc, chiều ngang hoặc cả ngang và dọc.
    • Nếu cắt theo chiều dọc, bạn sẽ chia bức ảnh làm 3 phần: Left, center và right. Trong trường hợp này, phần left và right là không co/dãn theo chiều ngang, phần center là phần co/dãn theo chiều ngang. (ảnh trên)
    • Nếu cắt theo chiều dọc, bạn sẽ chia bức ảnh làm 3 phần là top, center và bottom. Trong trường hợp này, phần top và bottom là không co/dãn theo chiều dọc, phần center là phần co/dãn theo chiều dọc (ảnh trên).
    • Nếu cắt theo chiều cả ngang và dọc, bạn sẽ chia bức ảnh làm 9 phần như ảnh trên và từng phần sẽ co/dãn như ảnh dưới.

    Cắt ảnh bằng Xcode Slicing Feature:

    1. Chọn vào phần Assets.xcassets trong Xcode.
    2. Chọn ảnh cần cắt.
    3. Chọn Editor -> Show Slicing hoặc chọn nút Show Slicing ở góc dưới bên phải.
    4. Chon Start Slicing
    1. Chọn cắt ảnh theo chiều ngang, chiều dọc hoặc cả ngang và dọc.
    1. Kéo các thanh để xác định phần bên phải, phần bên trái.
    • Phần 1: Phần không bị co/dãn bên trái theo chiều ngang.
    • Phần 2: Phần sẽ bị co/dãn theo chiều ngang.
    • Phần 3: Phần sẽ bị xóa khỏi ảnh mới.
    • Phần 4. Phần không bị co/dãn bên phải theo chiều ngang.
    Kết qủa: Ảnh bị co dãn phần ở giữa nhưng 2 bên không bị co dãn -> ảnh không có cảm giác bị méo

    Cắt ảnh bằng code:

    Bạn tạo ra 1 ảnh mới bằng cách cắt 1 ảnh có sẵn bằng hàm: resizableImage(withCapInsets:) hoặc resizableImage(withCapInsets:resizingMode:)

    if let img = UIImage(named: "button") {
        let resizable = img.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24), resizingMode: .stretch)
    }
    • Khai báo top và bottom # 0 nếu bạn muốn cắt ảnh theo chiều ngang.
    • Khai báo left và right # 0 nếu bạn muốn cắt ảnh theo chiều dọc.
    • Khai báo top, left, right và bottom # 0 nếu bạn muốn cắt ảnh theo cả ngang và dọc.

    Hi vọng qua bài viết này, bạn sẽ không gặp khó khăn khi apply 1 ảnh cho các buttons, views có size khác nhau.

    Link tham khảo: https://developer.apple.com/documentation/uikit/uiimage

  • [React] Những điều có thể bạn chưa biết về Function Component

    [React] Những điều có thể bạn chưa biết về Function Component

    Chắc hẳn khi đọc bài này bạn cũng đã từng nghe nói đến các khái niệm Function Component và Class Component hay Stateless Component và Stateful Component. Cũng có thể bạn cũng từng nghe Function Component là Stateless Component. Vậy nó có thực sự là như thế. Trong bài viết này chúng ta cùng nhau tìm hiểu nhé.

    Nội dung bài viết gồm các phần sau:

    • Function Component là gì?
    • Function Component có props không?
    • Function component và sức mạnh đến từ React Hook

    Function component là gi?

    Function component là React Component được viết bằng Pure Function. Dễ hiểu hơn thì nó được viết theo dạng sau:

    export function Counter() {
      return <div>count</div>;
    }
    

    Function Component có props không?

    Có bạn sẽ đặt câu hỏi sau khi nhìn thấy cách viết trên, Function Component có props không? Câu trả lời là có. Bạn có thể truyền props cho component như sau:

    import React from 'react';
    
    export function Counter(props) {
      return <div>{props.count}</div>;
    }
    

    Và bạn vẫn có thể khai báo propTypes cho component như bình thường

    Counter.propTypes = {
      count: PropTypes.number
    };
    

    Mọi thứ đều ổn đến lúc này và có lẽ bạn cũng có nghe ai đó nhắc đến Stateless component. Có thể bạn cũng nghe ai đó nói Function Component là Stateless component. Vậy thì Function component có thực sự là Stateless không?

    Function component và sức mạnh đến từ React Hook

    Trước khi React Hooks ra đời, Function component thực sự là Stateless component. Đơn giản bởi bạn chẳng có cách nào thêm state cho nó cả. Function component khi đó chỉ có thể được dùng để render HTML với props truyền vào mà thôi.

    Khi React Hooks ra đời thì lại là câu chuyện khác. Function component có thể có state của riêng nó. Vậy làm sao để thêm state cho Function component? Với useState bạn có thể thêm state cho nó.

    Thay đổi một chút đoạn source code bên trên như sau là Functiona component của bạn đã support state rồi đấy.

    import React, { useState } from 'react';
    import PropTypes from 'prop-types';
    
    export function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <button onClick={() => setCount(count - 1)}>-</button>
          {count}
          <button onClick={() => setCount(count + 1)}>+</button>
        </div>
      );
    }
    
    Counter.propTypes = {
      count: PropTypes.number
    };
    

    Kết luận lại là Function Component sẽ là Stateless Component nếu không có sự trợ giúp của React Hooks. Với React Hooks thì Function Component đã có toàn bộ sức mạnh như Class Component rồi.

    Cám ơn các bạn đã dành thời gian theo dõi bài viết. Hy vọng bày viết sẽ giúp ích cho các bạn.

  • [MacOS] Hướng dẫn cài đặt Oracle JDK

    [MacOS] Hướng dẫn cài đặt Oracle JDK

    Mặc định thì Oracle JDK sẽ được chọn cài đặt trên MacOS. Do đó nếu muốn sử dụng Oracle JDK thì bạn cần phải cài đặt lại. Trong bài viết này tôi sẽ hướng dẫn các bạn cài đặt Oracle JDK.

    Homebrew

    • Nếu bạn chưa cài đặt brew thì có thể sử dụng lệnh sau để tiến hành cài đặt
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
    
    • Nếu đã cài đặt rồi thì tiến hành update lastest brew như sau:
    hieunv@HieuNV ~ % brew update && brew upgrade
    Updated 1 tap (homebrew/core).
    ==> New Formulae
    swift-sh
    ==> Updated Formulae
    apache-spark               jetty                      xcodegen
    docker-slim                vim                        zsh-syntax-highlighting
    Updating Homebrew...
    

    Kiểm tra caskjava

    brew cask info java
    

    Nếu homebrew/cask chưa được cài đặt thì nó sẽ tự động cài đặt luôn

    hieunv@HieuNV ~ % brew cask info java
    ==> Tapping homebrew/cask
    Cloning into '/usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask'...
    remote: Enumerating objects: 3655, done.
    remote: Counting objects: 100% (3655/3655), done.
    remote: Compressing objects: 100% (3648/3648), done.
    remote: Total 3655 (delta 26), reused 510 (delta 5), pack-reused 0
    Receiving objects: 100% (3655/3655), 1.23 MiB | 215.00 KiB/s, done.
    Resolving deltas: 100% (26/26), done.
    Tapped 1 command and 3543 casks (3,660 files, 4.0MB).
    java: 13.0.2,8:d4173c853231432d94f001e99d882ca7
    https://openjdk.java.net/
    Not installed
    From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/java.rb
    ==> Name
    OpenJDK Java Development Kit
    ==> Artifacts
    jdk-13.0.2.jdk -> /Library/Java/JavaVirtualMachines/openjdk-13.0.2.jdk (Generic Artifact)
    
    • Nếu đã cài đặt rồi bạn sẽ nhận được thông tin về phiên bản java đã được cài đặt
    hieunv@HieuNV ~ % brew cask info java
    java: 13.0.2,8:d4173c853231432d94f001e99d882ca7
    https://openjdk.java.net/
    Not installed
    From: https://github.com/Homebrew/homebrew-cask/blob/master/Casks/java.rb
    ==> Name
    OpenJDK Java Development Kit
    ==> Artifacts
    jdk-13.0.2.jdk -> /Library/Java/JavaVirtualMachines/openjdk-13.0.2.jdk (Generic Artifact)
    

    Tiến hành cài đặt Oracle JDK sử dụng brew cask

    hieunv@HieuNV ~ % brew cask install oracle-jdk
    ==> Caveats
    Installing oracle-jdk means you have AGREED to the license at:
      https://www.oracle.com/technetwork/java/javase/terms/license/javase-license.html
    
    ==> Downloading https://download.oracle.com/otn-pub/java/jdk/13.0.2+8/d4173c8532
    ==> Downloading from https://download.oracle.com/otn-pub/java/jdk/13.0.2+8/d4173
    ######################################################################## 100.0%
    ==> Verifying SHA-256 checksum for Cask 'oracle-jdk'.
    ==> Installing Cask oracle-jdk
    ==> Running installer for oracle-jdk; your password may be necessary.
    ==> Package installers may write to any location; options such as --appdir are i
    Password:
    installer: Package name is JDK 13.0.2
    installer: Installing at base path /
    installer: The install was successful.
    ?  oracle-jdk was successfully installed!
    

    Kiểm tra phiên bản java sau khi cài đặt

    hieunv@HieuNV ~ % java --version
    java 13.0.2 2020-01-14
    Java(TM) SE Runtime Environment (build 13.0.2+8)
    Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)
    
    hieunv@HieuNV ~ % javac --version
    javac 13.0.2
    

    setting JAVA_HOME

    Thêm export JAVA_HOME=$(/usr/libexec/java_home) vào ~/.zshrc

    echo 'export JAVA_HOME=$(/usr/libexec/java_home)' >> ~/.zshrc
    

    Kiểm tra biến JAVA_HOME

    Đóng Termial sau đó bật lại và kiểm tra biến JAVA_HOME

    hieunv@HieuNV libexec % echo $JAVA_HOME
    /Library/Java/JavaVirtualMachines/jdk-13.0.2.jdk/Contents/Home
    

    Như vậy là bạn đã tiến hành cài đặt thành công Oracle Java rồi.
    Tài liệu tham khảo
    https://emcorrales.com/blog/install-oracle-jdk-macos-homebrew

  • 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