1. บทนำ
FFI (อินเทอร์เฟซสำหรับฟังก์ชันต่างประเทศ) ของ Dart ช่วยให้แอป Flutter ใช้ประโยชน์จากไลบรารีเนทีฟที่มีอยู่ซึ่งแสดง C API ได้ Dart รองรับ FFI ใน Android, iOS, Windows, macOS และ Linux สำหรับเว็บ Dart จะรองรับการทำงานร่วมกันของ JavaScript แต่จะไม่ครอบคลุมเรื่องดังกล่าวใน Codelab นี้
สิ่งที่คุณจะสร้าง
ใน Codelab นี้ คุณสร้างปลั๊กอินสำหรับมือถือและเดสก์ท็อปที่ใช้ไลบรารี C คุณสามารถใช้ API นี้เขียนตัวอย่างแอปง่ายๆ ที่ใช้ปลั๊กอิน ปลั๊กอินและแอปของคุณจะทำสิ่งต่อไปนี้
- นําเข้าซอร์สโค้ดของไลบรารี C ไปยังปลั๊กอิน Flutter ใหม่
- ปรับแต่งปลั๊กอินเพื่ออนุญาตให้สร้างใน Windows, macOS, Linux, Android และ iOS
- สร้างแอปพลิเคชันที่ใช้ปลั๊กอินสำหรับ JavaScript REPL (อ่านวนซ้ำการพิมพ์)
สิ่งที่คุณจะได้เรียนรู้
ใน Codelab นี้ คุณจะได้เรียนรู้ความรู้ที่นำไปใช้ได้จริงในการสร้างปลั๊กอิน Flutter ตาม FFI ทั้งบนแพลตฟอร์มเดสก์ท็อปและอุปกรณ์เคลื่อนที่ ซึ่งรวมถึงสิ่งต่อไปนี้
- การสร้างเทมเพลตปลั๊กอิน Flutter ที่ใช้ Dart FFI
- การใช้แพ็กเกจ
ffigen
เพื่อสร้างโค้ดการเชื่อมโยงสำหรับไลบรารี C - ใช้ CMake เพื่อสร้างปลั๊กอิน Flutter FFI สำหรับ Android, Windows และ Linux
- การใช้ CocoaPods เพื่อสร้างปลั๊กอิน Flutter FFI สำหรับ iOS และ macOS
สิ่งที่คุณต้องมี
- Android Studio 4.1 ขึ้นไปสำหรับการพัฒนา Android
- Xcode 13 ขึ้นไปสำหรับการพัฒนา iOS และ macOS
- Visual Studio 2022 หรือเครื่องมือสร้าง 2022 ของ Visual Studio ที่มาพร้อมกับ "การพัฒนาเดสก์ท็อปด้วย C++" ภาระงานสำหรับการพัฒนาเดสก์ท็อปของ Windows
- Flutter SDK
- เครื่องมือบิลด์ที่จำเป็นสำหรับแพลตฟอร์มที่คุณจะพัฒนา (เช่น CMake, CocoaPods เป็นต้น)
- LLVM สำหรับแพลตฟอร์มที่คุณกำลังพัฒนา
ffigen
ใช้ชุดเครื่องมือคอมไพเลอร์ LLVM เพื่อแยกวิเคราะห์ไฟล์ส่วนหัว C เพื่อสร้างการเชื่อมโยง FFI ที่เปิดเผยใน Dart - ตัวแก้ไขโค้ด เช่น โค้ด Visual Studio
2. เริ่มต้นใช้งาน
เครื่องมือ ffigen
เป็นส่วนหนึ่งของ Flutter ล่าสุด คุณยืนยันว่าการติดตั้ง Flutter กำลังใช้งานรุ่นที่เสถียรในปัจจุบันได้โดยการเรียกใช้คำสั่งต่อไปนี้
$ flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel stable, 3.3.9, on macOS 13.1 22C65 darwin-arm, locale en) [✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 14.1) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.2) [✓] IntelliJ IDEA Community Edition (version 2022.2.2) [✓] VS Code (version 1.74.0) [✓] Connected device (2 available) [✓] HTTP Host Availability • No issues found!
ตรวจสอบยืนยันเอาต์พุต flutter doctor
ว่าคุณกำลังใช้เวอร์ชันเสถียร และไม่มีรุ่น Flutter ที่เสถียรล่าสุดให้ใช้งานได้ หากคุณไม่ได้ใช้งานเวอร์ชันเสถียรหรือมีรุ่นที่ใหม่กว่าที่พร้อมใช้งาน ให้เรียกใช้คำสั่ง 2 รายการต่อไปนี้เพื่อทำให้เครื่องมือ Flutter ของคุณทำงานได้เร็วยิ่งขึ้น
$ flutter channel stable $ flutter upgrade
คุณเรียกใช้โค้ดใน Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้
- คอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์ (ปลั๊กอินและแอปตัวอย่างในเวอร์ชันเดสก์ท็อป)
- อุปกรณ์ Android หรือ iOS จริงที่เชื่อมต่อกับคอมพิวเตอร์และตั้งค่าเป็นโหมดนักพัฒนาซอฟต์แวร์
- iOS Simulator (ต้องติดตั้งเครื่องมือ Xcode)
- โปรแกรมจำลองของ Android (ต้องตั้งค่าใน Android Studio)
3. สร้างเทมเพลตปลั๊กอิน
การเริ่มต้นใช้งานการพัฒนาปลั๊กอิน Flutter
Flutter มาพร้อมกับเทมเพลตสำหรับปลั๊กอินที่ช่วยให้คุณเริ่มต้นใช้งานได้ง่าย เมื่อสร้างเทมเพลตปลั๊กอิน คุณสามารถระบุภาษาที่ต้องการใช้ได้
เรียกใช้คำสั่งต่อไปนี้ในไดเรกทอรีการทำงานเพื่อสร้างโครงการโดยใช้เทมเพลตปลั๊กอิน
$ flutter create --template=plugin_ffi \ --platforms=android,ios,linux,macos,windows ffigen_app
พารามิเตอร์ --platforms
ระบุแพลตฟอร์มที่ปลั๊กอินของคุณจะรองรับ
คุณสามารถตรวจสอบเลย์เอาต์ของโปรเจ็กต์ที่สร้างขึ้นได้โดยใช้คำสั่ง tree
หรือโปรแกรมสำรวจไฟล์ของระบบปฏิบัติการ
$ tree -L 2 ffigen_app ffigen_app ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android │ ├── build.gradle │ ├── ffigen_app_android.iml │ ├── local.properties │ ├── settings.gradle │ └── src ├── example │ ├── README.md │ ├── analysis_options.yaml │ ├── android │ ├── ffigen_app_example.iml │ ├── ios │ ├── lib │ ├── linux │ ├── macos │ ├── pubspec.lock │ ├── pubspec.yaml │ └── windows ├── ffigen.yaml ├── ffigen_app.iml ├── ios │ ├── Classes │ └── ffigen_app.podspec ├── lib │ ├── ffigen_app.dart │ └── ffigen_app_bindings_generated.dart ├── linux │ └── CMakeLists.txt ├── macos │ ├── Classes │ └── ffigen_app.podspec ├── pubspec.lock ├── pubspec.yaml ├── src │ ├── CMakeLists.txt │ ├── ffigen_app.c │ └── ffigen_app.h └── windows └── CMakeLists.txt 17 directories, 26 files
คุณควรสละเวลาสักครู่เพื่อดูโครงสร้างไดเรกทอรีเพื่อให้เข้าใจถึงสิ่งที่ระบบสร้างขึ้นและตำแหน่งที่ตั้งของไดเรกทอรีนั้น เทมเพลต plugin_ffi
จะวางโค้ด Dart ของปลั๊กอินไว้ใน lib
ไดเรกทอรีเฉพาะแพลตฟอร์มที่ชื่อ android
, ios
, linux
, macos
และ windows
และที่สำคัญที่สุดคือไดเรกทอรี example
สำหรับนักพัฒนาซอฟต์แวร์ที่เคยพัฒนา Flutter ตามปกติ โครงสร้างนี้อาจดูแปลกเพราะไม่มีการกำหนดไฟล์ที่ระดับบนสุดไว้ ปลั๊กอินสำหรับรวมกันอยู่ในโปรเจ็กต์ Flutter อื่นๆ แต่คุณจะต้องใส่โค้ดในไดเรกทอรี example
เพื่อให้มั่นใจว่าโค้ดปลั๊กอินทำงานได้
ได้เวลาเริ่มต้นใช้งานแล้ว
4. สร้างและเรียกใช้ตัวอย่าง
หากต้องการตรวจสอบว่ามีการติดตั้งระบบบิลด์และข้อกำหนดเบื้องต้นอย่างถูกต้องและทำงานของแต่ละแพลตฟอร์มที่รองรับ ให้สร้างและเรียกใช้แอปตัวอย่างที่สร้างขึ้นสำหรับแต่ละเป้าหมาย
หน้าต่าง
ตรวจสอบว่าคุณใช้ Windows เวอร์ชันที่รองรับ Codelab นี้เป็นที่ทราบกันว่าใช้งานได้บน Windows 10 และ Windows 11
คุณสามารถสร้างแอปพลิเคชันจากภายในตัวแก้ไขโค้ดหรือจากบรรทัดคำสั่งก็ได้
PS C:\Users\brett\Documents> cd .\ffigen_app\example\ PS C:\Users\brett\Documents\ffigen_app\example> flutter run -d windows Launching lib\main.dart on Windows in debug mode...Building Windows application... Syncing files to device Windows... 160ms Flutter run key commands. r Hot reload. R Hot restart. h List all available interactive commands. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). Running with sound null safety An Observatory debugger and profiler on Windows is available at: http://127.0.0.1:53317/OiKWpyHXxHI=/ The Flutter DevTools debugger and profiler on Windows is available at: http://127.0.0.1:9100?uri=http://127.0.0.1:53317/OiKWpyHXxHI=/
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ในลักษณะต่อไปนี้
Linux
ตรวจสอบว่าคุณกำลังใช้ Linux เวอร์ชันที่รองรับ Codelab นี้ใช้ Ubuntu 22.04.1
เมื่อคุณติดตั้งข้อกำหนดเบื้องต้นทั้งหมดที่ระบุในขั้นตอนที่ 2 แล้ว ให้เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
$ cd ffigen_app/example $ flutter run -d linux Launching lib/main.dart on Linux in debug mode... Building Linux application... Syncing files to device Linux... 504ms Flutter run key commands. r Hot reload. 🔥🔥🔥 R Hot restart. h List all available interactive commands. d Detach (terminate "flutter run" but leave application running). c Clear the screen q Quit (terminate the application on the device). 💪 Running with sound null safety 💪 An Observatory debugger and profiler on Linux is available at: http://127.0.0.1:36653/Wgek1JGag48=/ The Flutter DevTools debugger and profiler on Linux is available at: http://127.0.0.1:9103?uri=http://127.0.0.1:36653/Wgek1JGag48=/
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ในลักษณะต่อไปนี้
Android
สำหรับ Android คุณสามารถใช้ Windows, macOS หรือ Linux ในการคอมไพล์ได้ ก่อนอื่นให้ตรวจสอบว่าคุณมีอุปกรณ์ Android ที่เชื่อมต่อกับคอมพิวเตอร์สำหรับการพัฒนาซอฟต์แวร์หรือกำลังใช้อินสแตนซ์ Android Emulator (AVD) ตรวจสอบว่า Flutter เชื่อมต่อกับอุปกรณ์ Android หรือโปรแกรมจำลองได้โดยการเรียกใช้สิ่งต่อไปนี้
$ flutter devices 3 connected devices: sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 12 (API 32) (emulator) macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
macOS และ iOS
คุณต้องใช้คอมพิวเตอร์ macOS สําหรับการพัฒนา macOS และ iOS Flutter
เริ่มด้วยการเรียกใช้แอปตัวอย่างใน macOS ตรวจสอบอุปกรณ์ที่ Flutter เห็นอีกครั้ง
$ flutter devices 2 connected devices: macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
เรียกใช้แอปตัวอย่างโดยใช้โปรเจ็กต์ปลั๊กอินที่สร้างขึ้น
$ cd ffigen_app/example $ flutter run -d macos
คุณควรเห็นหน้าต่างแอปที่ทำงานอยู่ในลักษณะต่อไปนี้
สำหรับ iOS คุณสามารถใช้เครื่องจำลองหรืออุปกรณ์ฮาร์ดแวร์จริง หากใช้เครื่องจำลอง ให้เปิดโปรแกรมจำลองก่อน คำสั่ง flutter devices
แสดงรายการเครื่องจำลองเป็นหนึ่งในอุปกรณ์ที่พร้อมใช้งานแล้ว
$ flutter devices 3 connected devices: iPhone SE (3rd generation) (mobile) • 1BCBE334-7EC4-433A-90FD-1BC14F3BA41F • ios • com.apple.CoreSimulator.SimRuntime.iOS-16-1 (simulator) macOS (desktop) • macos • darwin-arm64 • macOS 13.1 22C65 darwin-arm Chrome (web) • chrome • web-javascript • Google Chrome 108.0.5359.98
เมื่อเครื่องจำลองเริ่มทำงานแล้ว ให้เรียกใช้: flutter run
$ cd ffigen_app/example $ flutter run -d iphone
เครื่องจำลอง iOS จะมีความสำคัญเหนือเป้าหมาย macOS คุณจึงข้ามการระบุอุปกรณ์ที่มีพารามิเตอร์ -d
ได้
ขอแสดงความยินดี คุณได้สร้างและเรียกใช้แอปพลิเคชันบนระบบปฏิบัติการที่แตกต่างกัน 5 ระบบได้สำเร็จ ต่อไป ให้สร้างปลั๊กอินเนทีฟและเชื่อมต่อกับปลั๊กอินจาก DART โดยใช้ FFI
5. การใช้ Duktape บน Windows, Linux และ Android
ไลบรารี C ที่คุณจะใช้ใน Codelab นี้คือ Duktape Duktape เป็นเครื่องมือ JavaScript แบบฝังได้ โดยเน้นที่ความสามารถในการพกพาและรอยเท้ากะทัดรัด ในขั้นตอนนี้ คุณจะต้องกำหนดค่าปลั๊กอินให้คอมไพล์ไลบรารี Duktape ลิงก์เข้ากับปลั๊กอิน จากนั้นเข้าถึงปลั๊กอินโดยใช้ FFI ของ Dart
ขั้นตอนนี้จะกำหนดค่าการผสานรวมให้ใช้งานได้บน Windows, Linux และ Android การผสานรวม iOS และ macOS ต้องมีการกำหนดค่าเพิ่มเติม (นอกเหนือจากที่ระบุไว้ในขั้นตอนนี้) เพื่อรวมไลบรารีที่คอมไพล์ไว้ในไฟล์ปฏิบัติการ Flutter ขั้นสุดท้าย การกำหนดค่าที่จำเป็นเพิ่มเติมมีอยู่ในขั้นตอนถัดไป
กำลังเรียกดู Duktape
ก่อนอื่น ให้รับสำเนาของซอร์สโค้ด duktape
โดยดาวน์โหลดจากเว็บไซต์ duktape.org
สำหรับ Windows คุณสามารถใช้ PowerShell กับ Invoke-WebRequest
ได้โดยทำดังนี้
PS> Invoke-WebRequest -Uri https://duktape.org/duktape-2.7.0.tar.xz -OutFile duktape-2.7.0.tar.xz
สำหรับ Linux ตัวเลือก wget
คือตัวเลือกที่ดี
$ wget https://duktape.org/duktape-2.7.0.tar.xz --2022-12-22 16:21:39-- https://duktape.org/duktape-2.7.0.tar.xz Resolving duktape.org (duktape.org)... 104.198.14.52 Connecting to duktape.org (duktape.org)|104.198.14.52|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1026524 (1002K) [application/x-xz] Saving to: ‘duktape-2.7.0.tar.xz' duktape-2.7.0.tar.x 100%[===================>] 1002K 1.01MB/s in 1.0s 2022-12-22 16:21:41 (1.01 MB/s) - ‘duktape-2.7.0.tar.xz' saved [1026524/1026524]
ไฟล์นี้เป็นที่เก็บถาวรของ tar.xz
ใน Windows ตัวเลือกหนึ่งคือดาวน์โหลดเครื่องมือ 7Zip และใช้เครื่องมือดังนี้
PS> 7z x .\duktape-2.7.0.tar.xz 7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15 Scanning the drive for archives: 1 file, 1026524 bytes (1003 KiB) Extracting archive: .\duktape-2.7.0.tar.xz -- Path = .\duktape-2.7.0.tar.xz Type = xz Physical Size = 1026524 Method = LZMA2:26 CRC64 Streams = 1 Blocks = 1 Everything is Ok Size: 19087360 Compressed: 1026524
คุณต้องเรียกใช้ 7z 2 ครั้ง อย่างแรกคือยกเลิกการเก็บถาวรการบีบอัด xz และครั้งที่ 2 เพื่อขยายที่เก็บถาวร tar
PS> 7z x .\duktape-2.7.0.tar 7-Zip 22.01 (x64) : Copyright (c) 1999-2022 Igor Pavlov : 2022-07-15 Scanning the drive for archives: 1 file, 19087360 bytes (19 MiB) Extracting archive: .\duktape-2.7.0.tar -- Path = .\duktape-2.7.0.tar Type = tar Physical Size = 19087360 Headers Size = 543232 Code Page = UTF-8 Characteristics = GNU ASCII Everything is Ok Folders: 46 Files: 1004 Size: 18281564 Compressed: 19087360
ในสภาพแวดล้อม Linux สมัยใหม่ tar
จะแยกเนื้อหาในขั้นตอนเดียวดังนี้
$ tar xvf duktape-2.7.0.tar.xz x duktape-2.7.0/ x duktape-2.7.0/README.rst x duktape-2.7.0/Makefile.sharedlibrary x duktape-2.7.0/Makefile.coffee x duktape-2.7.0/extras/ x duktape-2.7.0/extras/README.rst x duktape-2.7.0/extras/module-node/ x duktape-2.7.0/extras/module-node/README.rst x duktape-2.7.0/extras/module-node/duk_module_node.h x duktape-2.7.0/extras/module-node/Makefile [... and many more files]
การติดตั้ง LLVM
หากต้องการใช้ ffigen
คุณต้องติดตั้ง LLVM ซึ่ง ffigen
ใช้เพื่อแยกวิเคราะห์ส่วนหัว C เรียกใช้คำสั่งต่อไปนี้ใน Windows
PS> winget install -e --id LLVM.LLVM Found LLVM [LLVM.LLVM] Version 15.0.5 This application is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Downloading https://github.com/llvm/llvm-project/releases/download/llvmorg-15.0.5/LLVM-15.0.5-win64.exe ██████████████████████████████ 277 MB / 277 MB Successfully verified installer hash Starting package install... Successfully installed
กำหนดค่าเส้นทางของระบบเพื่อเพิ่ม C:\Program Files\LLVM\bin
ไปยังเส้นทางการค้นหาไบนารีของคุณเพื่อดำเนินการติดตั้ง LLVM บนเครื่อง Windows ให้เสร็จสมบูรณ์ คุณสามารถทดสอบได้ว่าติดตั้งอย่างถูกต้องหรือไม่ โดยทำตามขั้นตอนต่อไปนี้
PS> clang --version clang version 15.0.5 Target: x86_64-pc-windows-msvc Thread model: posix InstalledDir: C:\Program Files\LLVM\bin
สำหรับ Ubuntu สามารถติดตั้ง Dependency LLVM ได้ดังนี้ Linux ดิสทริบิวชันอื่นๆ มีทรัพยากร Dependency ที่คล้ายกันสำหรับ LLVM และ Clang
$ sudo apt install libclang-dev [sudo] password for brett: Reading package lists... Done Building dependency tree... Done Reading state information... Done The following additional packages will be installed: libclang-15-dev The following NEW packages will be installed: libclang-15-dev libclang-dev 0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded. Need to get 26.1 MB of archives. After this operation, 260 MB of additional disk space will be used. Do you want to continue? [Y/n] y Get:1 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-15-dev amd64 1:15.0.2-1 [26.1 MB] Get:2 http://archive.ubuntu.com/ubuntu kinetic/universe amd64 libclang-dev amd64 1:15.0-55.1ubuntu1 [2962 B] Fetched 26.1 MB in 7s (3748 kB/s) Selecting previously unselected package libclang-15-dev. (Reading database ... 85898 files and directories currently installed.) Preparing to unpack .../libclang-15-dev_1%3a15.0.2-1_amd64.deb ... Unpacking libclang-15-dev (1:15.0.2-1) ... Selecting previously unselected package libclang-dev. Preparing to unpack .../libclang-dev_1%3a15.0-55.1ubuntu1_amd64.deb ... Unpacking libclang-dev (1:15.0-55.1ubuntu1) ... Setting up libclang-15-dev (1:15.0.2-1) ... Setting up libclang-dev (1:15.0-55.1ubuntu1) ...
ตามข้างต้น คุณทดสอบการติดตั้ง LLVM บน Linux ได้ดังนี้
$ clang --version Ubuntu clang version 15.0.2-1 Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
กำลังกำหนดค่า ffigen
เทมเพลตที่สร้าง pubpsec.yaml
ระดับบนสุดอาจมีแพ็กเกจ ffigen
เวอร์ชันเก่า เรียกใช้คำสั่งต่อไปนี้เพื่ออัปเดตทรัพยากร Dependency ของ DART ในโปรเจ็กต์ปลั๊กอิน
$ flutter pub upgrade --major-versions
ตอนนี้แพ็กเกจ ffigen
เป็นเวอร์ชันล่าสุดแล้ว ขั้นตอนถัดไปให้กำหนดค่าไฟล์ที่ ffigen
จะใช้ในการสร้างไฟล์การเชื่อมโยง แก้ไขเนื้อหาของไฟล์ ffigen.yaml
ของโปรเจ็กต์ให้ตรงกับรายการต่อไปนี้
ffigen.yaml
# Run with `flutter pub run ffigen --config ffigen.yaml`.
name: DuktapeBindings
description: |
Bindings for `src/duktape.h`.
Regenerate bindings with `flutter pub run ffigen --config ffigen.yaml`.
output: 'lib/duktape_bindings_generated.dart'
headers:
entry-points:
- 'src/duktape.h'
include-directives:
- 'src/duktape.h'
preamble: |
// ignore_for_file: always_specify_types
// ignore_for_file: camel_case_types
// ignore_for_file: non_constant_identifier_names
comments:
style: any
length: full
ignore-source-errors: true
การกำหนดค่านี้ประกอบด้วยไฟล์ส่วนหัว C ที่จะส่งต่อไปยัง LLVM, ไฟล์เอาต์พุตที่จะสร้าง คำอธิบายที่จะใส่ไว้ที่ด้านบนสุดของไฟล์ และส่วนก่อนคำสั่งที่ใช้เพื่อเพิ่มคำเตือน Lint
มีรายการการกำหนดค่า 1 รายการที่ตอนท้ายของไฟล์ที่ควรมีคำอธิบายเพิ่มเติม ตั้งแต่เวอร์ชัน 11.0.0 ของ ffigen
เครื่องมือสร้างการเชื่อมโยงจะไม่สร้างการเชื่อมโยงโดยค่าเริ่มต้นหากมีคำเตือนหรือข้อผิดพลาดที่ clang
สร้างขึ้นเมื่อแยกวิเคราะห์ไฟล์ส่วนหัว
ไฟล์ส่วนหัว Duktape ตามที่เขียนไว้จะทริกเกอร์ clang
ใน macOS เพื่อสร้างคำเตือนเนื่องจากไม่มีตัวระบุประเภทความสามารถในการเว้นว่างในเคอร์เซอร์ของ Duktape Duktape ต้องเพิ่มตัวระบุประเภทเหล่านี้ลงในฐานของโค้ด Duktape เพื่อให้รองรับ Duktape สำหรับ macOS และ iOS อย่างเต็มรูปแบบ ในระหว่างนี้ เราจะตัดสินใจที่จะเพิกเฉยต่อคำเตือนเหล่านี้โดยตั้งค่าสถานะ ignore-source-errors
เป็น true
ในแอปพลิเคชันเวอร์ชันที่ใช้งานจริง คุณควรลบคำเตือนของคอมไพเลอร์ทั้งหมดออกก่อนจัดส่งแอปพลิเคชันของคุณ อย่างไรก็ตาม การดำเนินการดังกล่าวสำหรับ Duktape อยู่นอกขอบเขตของ Codelab นี้
ดูรายละเอียดเพิ่มเติมเกี่ยวกับคีย์และค่าอื่นๆ ได้ในเอกสารประกอบเกี่ยวกับ ffigen
คุณต้องคัดลอกไฟล์ Duktape ที่ต้องการจากการแจกจ่าย Duktape ไปยังตำแหน่งที่กำหนดค่า ffigen
เพื่อค้นหาไฟล์
$ cp duktape-2.7.0/src/duktape.c src/ $ cp duktape-2.7.0/src/duktape.h src/ $ cp duktape-2.7.0/src/duk_config.h src/
ในทางเทคนิค คุณเพียงแค่ต้องคัดลอกข้าม duktape.h
สำหรับ ffigen
แต่คุณกำลังจะกำหนดค่า CMake เพื่อสร้างไลบรารีที่ต้องใช้ทั้ง 3 อย่าง เรียกใช้ ffigen
เพื่อสร้างการเชื่อมโยงใหม่
$ flutter pub run ffigen --config ffigen.yaml Running in Directory: '/home/brett/GitHub/codelabs/ffigen_codelab/step_05' Input Headers: [./src/duktape.h] [WARNING]: No definition found for declaration - (Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread [WARNING]: No definition found for declaration - (Cursor) spelling: duk_hthread, kind: 2, kindSpelling: StructDecl, type: 105, typeSpelling: struct duk_hthread, usr: c:@S@duk_hthread [WARNING]: Generated declaration '__va_list_tag' start's with '_' and therefore will be private. Finished, Bindings generated in /home/brett/GitHub/codelabs/ffigen_codelab/step_05/./lib/duktape_bindings_generated.dart
คุณจะเห็นคำเตือนที่แตกต่างกันไปในแต่ละระบบปฏิบัติการ คุณไม่จำเป็นต้องดำเนินการใดๆ ในขณะนี้เนื่องจาก Duktape 2.7.0 เป็นโปรแกรมที่คอมไพล์ clang
ใน Windows, Linux และ macOS
การกำหนดค่า CMake
CMake เป็นระบบการสร้างระบบบิลด์ ปลั๊กอินนี้ใช้ CMake ในการสร้างระบบบิลด์สำหรับ Android, Windows และ Linux เพื่อรวม Duktape ในไบนารี Flutter ที่สร้างขึ้น คุณต้องแก้ไขไฟล์การกำหนดค่า CMake ที่เทมเพลตสร้างขึ้นโดยทำดังนี้
src/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(ffigen_app_library VERSION 0.0.1 LANGUAGES C)
add_library(ffigen_app SHARED
duktape.c # Modify
)
set_target_properties(ffigen_app PROPERTIES
PUBLIC_HEADER duktape.h # Modify
PRIVATE_HEADER duk_config.h # Add
OUTPUT_NAME "ffigen_app" # Add
)
# Add from here...
if (WIN32)
set_target_properties(ffigen_app PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
endif (WIN32)
# ... to here.
target_compile_definitions(ffigen_app PUBLIC DART_SHARED_LIB)
การกำหนดค่า CMake จะเพิ่มไฟล์ต้นฉบับ และที่สำคัญกว่านั้นคือแก้ไขลักษณะการทำงานเริ่มต้นของไฟล์ไลบรารีที่สร้างขึ้นใน Windows เพื่อส่งออกสัญลักษณ์ C ทั้งหมดโดยค่าเริ่มต้น นี่คือ CMake ช่วยโอนไลบรารีแบบ Unix หรือที่เรียกว่า Duktape ไปสู่โลกของ Windows
แทนที่เนื้อหาของ lib/ffigen_app.dart
ด้วยข้อมูลต่อไปนี้
lib/ffigen_app.dart
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'duktape_bindings_generated.dart';
const String _libName = 'ffigen_app';
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
final DuktapeBindings _bindings = DuktapeBindings(_dylib);
class Duktape {
Duktape() {
ctx =
_bindings.duk_create_heap(nullptr, nullptr, nullptr, nullptr, nullptr);
}
void evalString(String jsCode) {
var nativeUtf8 = jsCode.toNativeUtf8();
_bindings.duk_eval_raw(
ctx,
nativeUtf8.cast<Char>(),
0,
0 |
DUK_COMPILE_EVAL |
DUK_COMPILE_SAFE |
DUK_COMPILE_NOSOURCE |
DUK_COMPILE_STRLEN |
DUK_COMPILE_NOFILENAME);
ffi.malloc.free(nativeUtf8);
}
int getInt(int index) {
return _bindings.duk_get_int(ctx, index);
}
void dispose() {
_bindings.duk_destroy_heap(ctx);
ctx = nullptr;
}
late Pointer<duk_hthread> ctx;
}
ไฟล์นี้มีหน้าที่โหลดไฟล์ไลบรารีลิงก์แบบไดนามิก (.so
สำหรับ Linux และ Android, .dll
สำหรับ Windows) และให้ Wrapper ที่แสดงอินเทอร์เฟซที่ไม่รู้จักของ DART สำหรับโค้ด C ที่สำคัญ
เนื่องจากไฟล์นี้นำเข้าแพ็กเกจ ffi
โดยตรง คุณจะต้องย้ายแพ็กเกจจาก dev_dependencies
ไปยัง dependencies
วิธีง่ายๆ ในการดำเนินการนี้คือเรียกใช้คำสั่งต่อไปนี้
$ dart pub add ffi
แทนที่เนื้อหาของ main.dart
ของตัวอย่างด้วยข้อมูลต่อไปนี้
example/lib/main.dart
import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
const String jsCode = '1+2';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Duktape duktape;
String output = '';
@override
void initState() {
super.initState();
duktape = Duktape();
setState(() {
output = 'Initialized Duktape';
});
}
@override
void dispose() {
duktape.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(fontSize: 25);
const spacerSmall = SizedBox(height: 10);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Duktape Test'),
),
body: Center(
child: Container(
padding: const EdgeInsets.all(10),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
output,
style: textStyle,
textAlign: TextAlign.center,
),
spacerSmall,
ElevatedButton(
child: const Text('Run JavaScript'),
onPressed: () {
duktape.evalString(jsCode);
setState(() {
output = '$jsCode => ${duktape.getInt(-1)}';
});
},
),
],
),
),
),
),
);
}
}
ตอนนี้คุณจะเรียกใช้แอปตัวอย่างอีกครั้งได้โดยใช้สิ่งต่อไปนี้
$ cd example $ flutter run
คุณควรเห็นแอปทำงานในลักษณะดังนี้
ภาพหน้าจอทั้ง 2 ภาพแสดงก่อนและหลังการกดปุ่มเรียกใช้ JavaScript ซึ่งสาธิตการเรียกใช้โค้ด JavaScript จาก Dart และแสดงผลลัพธ์บนหน้าจอ
Android
Android เป็นระบบปฏิบัติการ Linux ที่ใช้เคอร์เนล และค่อนข้างคล้ายกับ Linux ชุดเดสก์ท็อป ระบบบิลด์ CMake สามารถซ่อนความแตกต่างระหว่าง 2 แพลตฟอร์มส่วนใหญ่ได้ หากต้องการสร้างและเรียกใช้บน Android โปรดตรวจสอบว่าโปรแกรมจำลอง Android ทำงานอยู่ (หรือเชื่อมต่ออุปกรณ์ Android แล้ว) เรียกใช้แอป ดังตัวอย่างต่อไปนี้
$ cd example $ flutter run -d emulator-5554
ตอนนี้คุณควรเห็นตัวอย่างแอปที่ทำงานบน Android
6. การใช้ Duktape บน macOS และ iOS
ตอนนี้ได้เวลาทำให้ปลั๊กอินของคุณทำงานบน macOS และ iOS ซึ่งเป็น 2 ระบบปฏิบัติการที่มีความเกี่ยวข้องกันอย่างใกล้ชิดแล้ว เริ่มต้นด้วย macOS แม้ว่า CMake จะรองรับ macOS และ iOS แต่คุณไม่ต้องนำงานที่เคยทำสำหรับ Linux มาใช้ซ้ำและ Android เนื่องจาก Flutter ใน macOS และ iOS ใช้ CocoaPods ในการนําเข้าไลบรารี
ล้างข้อมูล
ในขั้นตอนก่อนหน้า คุณได้สร้างแอปพลิเคชันที่ใช้งานได้สำหรับ Android, Windows และ Linux อย่างไรก็ตาม มีไฟล์อีก 2-3 ไฟล์ที่เหลือจากเทมเพลตเดิมที่คุณต้องทำการล้าง ให้นําออกเลย ดังนี้
$ rm src/ffigen_app.c $ rm src/ffigen_app.h $ rm ios/Classes/ffigen_app.c $ rm macos/Classes/ffigen_app.c
macOS
Flutter บนแพลตฟอร์ม macOS ใช้ CocoaPods เพื่อนําเข้าโค้ด C และ C++ ซึ่งหมายความว่าต้องผสานรวมแพ็กเกจนี้เข้ากับโครงสร้างพื้นฐานของ CocoaPods หากต้องการนําโค้ด C ที่คุณกําหนดค่าไว้แล้วไปใช้ซ้ำด้วย CMake ในขั้นตอนก่อนหน้า คุณจะต้องเพิ่มไฟล์ส่งต่อไฟล์เดียวในโปรแกรมเรียกใช้แพลตฟอร์ม macOS
macos/Classes/duktape.c
#include "../../src/duktape.c"
ไฟล์นี้ใช้ศักยภาพของ C Preprocessor เพื่อรวมซอร์สโค้ดจากซอร์สโค้ดดั้งเดิมที่คุณตั้งค่าไว้ในขั้นตอนก่อนหน้า ดูรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานของการดำเนินการนี้ได้ที่ macos/ffigen_app.podspec
การเรียกใช้แอปพลิเคชันนี้มีรูปแบบเดียวกับที่คุณเคยเห็นบน Windows และ Linux
$ cd example $ flutter run -d macos
iOS
iOS กำหนดให้เพิ่มไฟล์ C สำหรับส่งต่อไฟล์เดียวเช่นกัน ซึ่งคล้ายกับการตั้งค่า macOS
ios/Classes/duktape.c
#include "../../src/duktape.c"
ด้วยไฟล์เดี่ยวนี้ ปลั๊กอินของคุณจะได้รับการกำหนดค่าให้ทำงานบน iOS ด้วยเช่นกัน เรียกใช้ตามปกติ
$ flutter run -d iPhone
ยินดีด้วย คุณได้ผสานรวมโค้ดแบบเนทีฟใน 5 แพลตฟอร์มสำเร็จแล้ว นี่คือพื้นที่สำหรับการเฉลิมฉลอง! บางทีอาจมีอินเทอร์เฟซผู้ใช้ที่ใช้งานได้มากขึ้น ซึ่งคุณจะสร้างในขั้นตอนถัดไป
7. ใช้ลูปการพิมพ์ของ Read Eval
การโต้ตอบกับภาษาโปรแกรมจะสนุกมากขึ้นในสภาพแวดล้อมแบบอินเทอร์แอกทีฟที่รวดเร็ว การใช้งานเดิมในสภาพแวดล้อมดังกล่าวคือ Read Eval Print Loop (REPL) ของ LISP คุณกำลังจะนำ Duktape ไปใช้ในกระบวนการนี้
เตรียมสิ่งต่างๆ ให้พร้อมสำหรับเวอร์ชันที่ใช้งานจริง
รหัสปัจจุบันที่โต้ตอบกับไลบรารี Duktape C จะถือว่าไม่มีข้อผิดพลาดใดๆ เกิดขึ้น อ้อ และไลบรารีลิงก์แบบไดนามิกของ Duktape ไม่โหลดขณะอยู่ระหว่างการทดสอบ คุณต้องทำการเปลี่ยนแปลงบางอย่างใน lib/ffigen_app.dart
เพื่อให้การผสานรวมนี้พร้อมใช้งาน
lib/ffigen_app.dart
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p; // Add this import
import 'duktape_bindings_generated.dart';
const String _libName = 'ffigen_app';
final DynamicLibrary _dylib = () {
if (Platform.isMacOS || Platform.isIOS) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return DynamicLibrary.open('build/macos/Build/Products/Debug'
'/$_libName/$_libName.framework/$_libName');
}
// ...to here.
return DynamicLibrary.open('$_libName.framework/$_libName');
}
if (Platform.isAndroid || Platform.isLinux) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return DynamicLibrary.open(
'build/linux/x64/debug/bundle/lib/lib$_libName.so');
}
// ...to here.
return DynamicLibrary.open('lib$_libName.so');
}
if (Platform.isWindows) {
// Add from here...
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return DynamicLibrary.open(p.canonicalize(
p.join(r'build\windows\runner\Debug', '$_libName.dll')));
}
// ...to here.
return DynamicLibrary.open('$_libName.dll');
}
throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();
final DuktapeBindings _bindings = DuktapeBindings(_dylib);
class Duktape {
Duktape() {
ctx =
_bindings.duk_create_heap(nullptr, nullptr, nullptr, nullptr, nullptr);
}
// Modify this function
String evalString(String jsCode) {
var nativeUtf8 = jsCode.toNativeUtf8();
final evalResult = _bindings.duk_eval_raw(
ctx,
nativeUtf8.cast<Char>(),
0,
0 |
DUK_COMPILE_EVAL |
DUK_COMPILE_SAFE |
DUK_COMPILE_NOSOURCE |
DUK_COMPILE_STRLEN |
DUK_COMPILE_NOFILENAME);
ffi.malloc.free(nativeUtf8);
if (evalResult != 0) {
throw _retrieveTopOfStackAsString();
}
return _retrieveTopOfStackAsString();
}
// Add this function
String _retrieveTopOfStackAsString() {
Pointer<Size> outLengthPtr = ffi.calloc<Size>();
final errorStrPtr = _bindings.duk_safe_to_lstring(ctx, -1, outLengthPtr);
final returnVal =
errorStrPtr.cast<ffi.Utf8>().toDartString(length: outLengthPtr.value);
ffi.calloc.free(outLengthPtr);
return returnVal;
}
void dispose() {
_bindings.duk_destroy_heap(ctx);
ctx = nullptr;
}
late Pointer<duk_hthread> ctx;
}
มีการขยายโค้ดสำหรับโหลดไลบรารีลิงก์แบบไดนามิกเพื่อรองรับกรณีที่มีการใช้ปลั๊กอินในโปรแกรมดำเนินการทดสอบ การดำเนินการนี้ทำให้มีการเขียนการทดสอบการผสานรวมที่ใช้ API นี้เป็นการทดสอบ Flutter มีการขยายเวลาโค้ดที่ใช้ประเมินสตริงโค้ด JavaScript เพื่อจัดการกับเงื่อนไขข้อผิดพลาดอย่างถูกต้อง เช่น โค้ดไม่สมบูรณ์หรือไม่ถูกต้อง โค้ดเพิ่มเติมนี้จะแสดงวิธีจัดการกรณีที่สตริงแสดงผลเป็นอาร์เรย์ไบต์และจำเป็นต้องแปลงเป็นสตริงของ Dart
การเพิ่มแพ็กเกจ
ในการสร้างการตอบกลับ คุณจะต้องแสดงการโต้ตอบระหว่างผู้ใช้กับเครื่องมือ JavaScript ของ Duktape ผู้ใช้ป้อนบรรทัดโค้ด แล้ว Duktape ตอบสนองด้วยผลของการคำนวณหรือข้อยกเว้น คุณจะใช้ freezed
เพื่อลดจำนวนโค้ดต้นแบบที่ต้องเขียน นอกจากนี้คุณยังใช้ google_fonts
เพื่อทำให้เนื้อหาที่แสดงมีธีมมากขึ้น และใช้ flutter_riverpod
สำหรับการจัดการสถานะ
เพิ่มทรัพยากร Dependency ที่จำเป็นลงในแอปตัวอย่าง
$ cd example $ dart pub add flutter_riverpod freezed_annotation google_fonts $ dart pub add -d build_runner freezed
จากนั้นสร้างไฟล์เพื่อบันทึกการโต้ตอบ REPL
example/lib/duktape_message.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'duktape_message.freezed.dart';
@freezed
class DuktapeMessage with _$DuktapeMessage {
factory DuktapeMessage.evaluate(String code) = DuktapeMessageCode;
factory DuktapeMessage.response(String result) = DuktapeMessageResponse;
factory DuktapeMessage.error(String log) = DuktapeMessageError;
}
คลาสนี้ใช้ฟีเจอร์ประเภทการรวมของ freezed
เพื่อให้แสดงรูปร่างของแต่ละเส้นที่แสดงใน "REPL" เป็น 1 ใน 3 ประเภทได้อย่างง่ายดาย ณ จุดนี้ โค้ดของคุณอาจแสดงข้อผิดพลาดบางอย่างในโค้ดนี้ เนื่องจากมีโค้ดเพิ่มเติมที่ต้องสร้าง โดยทำตามขั้นตอนต่อไปนี้
$ flutter pub run build_runner build
การดำเนินการนี้จะสร้างไฟล์ example/lib/duktape_message.freezed.dart
ซึ่งเป็นโค้ดที่คุณเพิ่งพิมพ์
ถัดไป คุณจะต้องแก้ไขไฟล์การกำหนดค่า macOS คู่หนึ่งเพื่อให้ google_fonts
ส่งคำขอเครือข่ายสำหรับข้อมูลแบบอักษรได้
example/macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- ...to here -->
</dict>
</plist>
example/macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- ...to here -->
</dict>
</plist>
การสร้างการตอบกลับ
เมื่อคุณได้อัปเดตเลเยอร์การผสานรวมเพื่อจัดการข้อผิดพลาด และคุณได้สร้างการนำเสนอข้อมูลสำหรับการโต้ตอบแล้ว ก็ถึงเวลาสร้างอินเทอร์เฟซผู้ใช้ของแอปตัวอย่าง
example/lib/main.dart
import 'package:ffigen_app/ffigen_app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'duktape_message.dart';
void main() {
runApp(const ProviderScope(child: DuktapeApp()));
}
final duktapeMessagesProvider =
StateNotifierProvider<DuktapeMessageNotifier, List<DuktapeMessage>>((ref) {
return DuktapeMessageNotifier(messages: <DuktapeMessage>[]);
});
class DuktapeMessageNotifier extends StateNotifier<List<DuktapeMessage>> {
DuktapeMessageNotifier({required List<DuktapeMessage> messages})
: duktape = Duktape(),
super(messages);
final Duktape duktape;
void eval(String code) {
state = [
DuktapeMessage.evaluate(code),
...state,
];
try {
final response = duktape.evalString(code);
state = [
DuktapeMessage.response(response),
...state,
];
} catch (e) {
state = [
DuktapeMessage.error('$e'),
...state,
];
}
}
}
class DuktapeApp extends StatelessWidget {
const DuktapeApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Duktape App',
home: DuktapeRepl(),
);
}
}
class DuktapeRepl extends ConsumerStatefulWidget {
const DuktapeRepl({
super.key,
});
@override
ConsumerState<DuktapeRepl> createState() => _DuktapeReplState();
}
class _DuktapeReplState extends ConsumerState<DuktapeRepl> {
final _controller = TextEditingController();
final _focusNode = FocusNode();
var _isComposing = false;
void _handleSubmitted(String text) {
_controller.clear();
setState(() {
_isComposing = false;
});
setState(() {
ref.read(duktapeMessagesProvider.notifier).eval(text);
});
_focusNode.requestFocus();
}
@override
Widget build(BuildContext context) {
final messages = ref.watch(duktapeMessagesProvider);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('Duktape REPL'),
elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
),
body: Column(
children: [
Flexible(
child: Ink(
color: Theme.of(context).scaffoldBackgroundColor,
child: SafeArea(
bottom: false,
child: ListView.builder(
padding: const EdgeInsets.all(8.0),
reverse: true,
itemBuilder: (context, idx) => messages[idx].when(
evaluate: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'> $str',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
),
),
),
response: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
'= $str',
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleMedium,
color: Colors.blue[800],
),
),
),
error: (str) => Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
str,
style: GoogleFonts.firaCode(
textStyle: Theme.of(context).textTheme.titleSmall,
color: Colors.red[800],
fontWeight: FontWeight.bold,
),
),
),
),
itemCount: messages.length,
),
),
),
),
const Divider(height: 1.0),
SafeArea(
top: false,
child: Container(
decoration: BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTextComposer(),
),
),
],
),
);
}
Widget _buildTextComposer() {
return IconTheme(
data: IconThemeData(color: Theme.of(context).colorScheme.secondary),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: Row(
children: [
Text('>', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(width: 4),
Flexible(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
border: InputBorder.none,
),
onChanged: (text) {
setState(() {
_isComposing = text.isNotEmpty;
});
},
onSubmitted: _isComposing ? _handleSubmitted : null,
focusNode: _focusNode,
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 4.0),
child: IconButton(
icon: const Icon(Icons.send),
onPressed: _isComposing
? () => _handleSubmitted(_controller.text)
: null,
),
),
],
),
),
);
}
}
โค้ดนี้มีหลายเหตุการณ์เกิดขึ้น แต่ก็ยังอยู่นอกเหนือขอบเขตของ Codelab นี้ที่จะอธิบายรายละเอียดทั้งหมด เราขอแนะนำให้คุณเรียกใช้โค้ด แล้วแก้ไขโค้ด หลังจากตรวจสอบเอกสารที่เหมาะสมแล้ว
$ cd example $ flutter run
8. ขอแสดงความยินดี
ยินดีด้วย คุณสร้างปลั๊กอินแบบ FFI ของ Flutter สำหรับ Windows, macOS, Linux, Android และ iOS เรียบร้อยแล้ว
หลังจากสร้างปลั๊กอินแล้ว คุณอาจต้องการแชร์ออนไลน์เพื่อให้คนอื่นใช้งานได้ คุณสามารถดูเอกสารฉบับเต็มเกี่ยวกับการเผยแพร่ปลั๊กอินไปยัง pub.dev ได้ในการพัฒนาแพ็กเกจปลั๊กอิน