Flutter प्लगिन में एफ़एफ़आई का इस्तेमाल करना

1. परिचय

Dart के FFI (विदेशी फ़ंक्शन इंटरफ़ेस) की मदद से, Flutter ऐप्लिकेशन ऐसे मौजूदा नेटिव लाइब्रेरी का इस्तेमाल कर सकते हैं जो किसी C API को बिना अनुमति के सार्वजनिक करती हैं. Dart की सुविधा, Android, iOS, Windows, macOS, और Linux पर एफ़एफ़आई के साथ काम करती है. वेब के लिए, Dart फ़ाइल JavaScript इंटरऑप का इस्तेमाल करती है, लेकिन वह विषय इस कोडलैब (कोड बनाना सीखना) में शामिल नहीं है.

आपको क्या बनाना होगा

इस कोडलैब में, सी लाइब्रेरी का इस्तेमाल करने वाला मोबाइल और डेस्कटॉप प्लगिन बनाया जाता है. इस एपीआई की मदद से, आपको प्लगिन का इस्तेमाल करने के लिए, उदाहरण के तौर पर एक ऐप्लिकेशन लिखना होगा. आपके प्लगिन और ऐप्लिकेशन से:

  • अपने नए Flutter प्लगिन में C लाइब्रेरी का सोर्स कोड इंपोर्ट करें
  • Windows, macOS, Linux, Android, और iOS पर इसे बनाने के लिए प्लगिन को पसंद के मुताबिक बनाएं
  • JavaScript के लिए प्लग इन का इस्तेमाल करने वाला ऐप्लिकेशन बनाएं REPL (रिवील प्रिंट लूप पढ़ें)

macOS ऐप्लिकेशन के तौर पर इस्तेमाल होने वाला Duktape REPL

आपको इनके बारे में जानकारी मिलेगी

इस कोडलैब में, आपको डेस्कटॉप और मोबाइल, दोनों प्लैटफ़ॉर्म पर FFI पर आधारित Flutter प्लगिन बनाने के लिए ज़रूरी व्यावहारिक जानकारी मिलेगी. इनमें ये शामिल हैं:

  • Dart FFI पर आधारित Flutter प्लगिन टेंप्लेट जनरेट करना
  • किसी C लाइब्रेरी के लिए बाइंडिंग कोड जनरेट करने के लिए, ffigen पैकेज का इस्तेमाल करना
  • Android, Windows, और Linux के लिए, Flutter FFI प्लगिन बनाने के लिए CMake का इस्तेमाल करना
  • iOS और macOS के लिए, Flutter FFI प्लग इन बनाने के लिए CocoaPods का इस्तेमाल करना

आपको इन चीज़ों की ज़रूरत होगी

  • Android डेवलपमेंट के लिए, Android Studio 4.1 या इसके बाद का वर्शन
  • iOS और macOS डेवलपमेंट के लिए, Xcode 13 या इसके बाद का वर्शन
  • "C++ के साथ डेस्कटॉप डेवलपमेंट" के साथ, Visual Studio 2022 या Visual Studio Build टूल 2022 Windows डेस्कटॉप डेवलपमेंट के लिए वर्कलोड
  • Flutter का SDK टूल
  • जिन प्लैटफ़ॉर्म को डेवलप किया जा रहा है उनके लिए ज़रूरी बिल्ड टूल. उदाहरण के लिए, CMake, CocoaPods वगैरह.
  • उन प्लैटफ़ॉर्म के लिए एलएलवीएम जिन पर आपको काम करना है. ffigen, एलएलवीएम कंपाइलर टूल सुइट का इस्तेमाल करके सी हेडर फ़ाइल को पार्स करता है, ताकि Dart में दिख रही एफ़एफ़आई बाइंडिंग को बनाया जा सके.
  • कोड एडिटर, जैसे कि Visual Studio Code.

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 रिलीज़ उपलब्ध नहीं है. अगर आपका ब्राउज़र स्टेबल वर्शन का इस्तेमाल नहीं कर रहा है या आपके हाल ही के वर्शन उपलब्ध हैं, तो अपने Flutter टूल को सही तरीके से इस्तेमाल करने के लिए इन दो निर्देशों का पालन करें.

$ flutter channel stable
$ flutter upgrade

इनमें से किसी भी डिवाइस का इस्तेमाल करके, इस कोडलैब में कोड को चलाया जा सकता है:

  • आपका डेवलपमेंट कंप्यूटर (आपके प्लगिन और उदाहरण ऐप्लिकेशन के डेस्कटॉप बिल्ड के लिए)
  • एक ऐसा Android या iOS डिवाइस जो आपके कंप्यूटर से कनेक्ट हो और डेवलपर मोड पर सेट हो
  • iOS सिम्युलेटर (Xcode टूल इंस्टॉल करना आवश्यक है)
  • Android Emulator (इसके लिए 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 टेंप्लेट, प्लगिन के लिए डार्ट कोड को lib में रखता है. साथ ही, प्लैटफ़ॉर्म के हिसाब से बनी डायरेक्ट्री, जिनका नाम android, ios, linux, macos, और windows है और सबसे ज़रूरी बात, example डायरेक्ट्री में इसे रखा जाता है.

सामान्य Flutter डेवलपमेंट के लिए इस्तेमाल किए जाने वाले डेवलपर को यह स्ट्रक्चर अलग लग सकता है. इसमें टॉप लेवल पर कोई एक्ज़ीक्यूटेबल परिभाषित नहीं है. प्लगिन को अन्य Flutter प्रोजेक्ट में शामिल करने के लिए बनाया जाता है. हालांकि, आपको example डायरेक्ट्री में कोड डालना होगा, ताकि यह पक्का किया जा सके कि आपका प्लगिन कोड काम कर रहा है.

चलिए, शुरुआत करें!

4. उदाहरण बनाएं और चलाएं

यह पक्का करने के लिए कि बिल्ड सिस्टम और उससे जुड़ी ज़रूरी शर्तें सही तरीके से इंस्टॉल की गई हैं और हर काम के प्लैटफ़ॉर्म पर काम कर रही हैं, हर टारगेट के लिए जनरेट किया गया उदाहरण ऐप्लिकेशन बनाएं और चलाएं.

Windows

देख लें कि आप Windows के साथ काम करने वाले वर्शन का इस्तेमाल कर रहे हैं. यह कोडलैब, 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=/

आपको इस तरह की 'चल रही ऐप्लिकेशन विंडो' दिखेगी:

Windows ऐप्लिकेशन के तौर पर चल रहा, टेंप्लेट से जनरेट किया गया FFI ऐप्लिकेशन

Linux

देख लें कि आप Linux के काम करने वाले वर्शन का इस्तेमाल कर रहे हैं. यह कोडलैब Ubuntu 22.04.1 का इस्तेमाल करता है.

दूसरे चरण में दी गई सभी ज़रूरी शर्तें इंस्टॉल करने के बाद, टर्मिनल में इन निर्देशों को चलाएं:

$ 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=/

आपको इस तरह की 'चल रही ऐप्लिकेशन विंडो' दिखेगी:

Linux ऐप्लिकेशन के तौर पर चल रहा, टेंप्लेट से जनरेट किया गया FFI ऐप्लिकेशन

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

Android Emulator में चल रहा, टेंप्लेट से जनरेट किया गया FFI ऐप्लिकेशन

macOS और iOS

macOS और iOS Flutter डेवलपमेंट के लिए, आपको macOS कंप्यूटर इस्तेमाल करना होगा.

सबसे पहले, 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

आपको इस तरह की 'चल रही ऐप्लिकेशन विंडो' दिखेगी:

Linux ऐप्लिकेशन के तौर पर चल रहा, टेंप्लेट से जनरेट किया गया FFI ऐप्लिकेशन

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 सिम्युलेटर में चल रहा, टेंप्लेट से जनरेट किया गया FFI ऐप्लिकेशन

macOS टारगेट के मुकाबले iOS सिम्युलेटर को प्राथमिकता मिलती है, ताकि आप -d पैरामीटर वाले डिवाइस की जानकारी देने से बच सकें.

बधाई हो, आपने पांच अलग-अलग ऑपरेटिंग सिस्टम पर एक ऐप्लिकेशन सफलतापूर्वक बना लिया है और उसे चला लिया है. इसके बाद, एफ़एफ़आई का इस्तेमाल करके नेटिव प्लगिन बनाना और Dart की मदद से उसे इंटरफ़ेस करना.

5. Windows, Linux, और Android पर Duktape का इस्तेमाल करना

इस कोडलैब में, C लाइब्रेरी का इस्तेमाल Duktape किया जाएगा. Duktape एक एम्बेड किया जा सकने वाला JavaScript इंजन है, जो पोर्टेबिलिटी और संक्षिप्त फ़ुटप्रिंट पर केंद्रित है. इस चरण में, आपको Duktape लाइब्रेरी को कंपाइल करने, उसे अपने प्लगिन से लिंक करने, और Dart के FFI का इस्तेमाल करके ऐक्सेस करने के लिए प्लगिन को कॉन्फ़िगर करना होगा.

यह चरण Windows, Linux, और Android पर काम करने के लिए इंटिग्रेशन को कॉन्फ़िगर करता है. iOS और macOS इंटिग्रेशन के लिए, कंपाइल की गई लाइब्रेरी को Flutter के लिए एक्ज़ीक्यूटेबल में शामिल करने के लिए अतिरिक्त कॉन्फ़िगरेशन (इस चरण में दी गई जानकारी के अलावा) की ज़रूरत होती है. अगले चरण में अतिरिक्त ज़रूरी कॉन्फ़िगरेशन के बारे में बताया गया है.

डक्टेप को फिर से पाना

सबसे पहले, duktape के सोर्स कोड की कॉपी पाने के लिए, duktape.org की वेबसाइट से इसे डाउनलोड करें.

Windows के लिए, Invoke-WebRequest के साथ PowerShell का इस्तेमाल किया जा सकता है:

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 चलाना होगा, पहले xz कंप्रेशन को संग्रह से निकालने के लिए, दूसरा टार संग्रह को विस्तृत करने के लिए.

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 का इस्तेमाल करने के लिए, आपको एलएलवीएम इंस्टॉल करना होगा. इसका इस्तेमाल 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 को अपने बाइनरी सर्च पाथ में जोड़ सकें. इससे, Windows मशीन पर LLVM के इंस्टॉल होने की प्रोसेस पूरी की जा सकेगी. यह जांच की जा सकती है कि डिवाइस सही तरीके से इंस्टॉल हुआ है या नहीं. इसके लिए, नीचे दिए गए निर्देशों का पालन करें.

PS> clang --version
clang version 15.0.5
Target: x86_64-pc-windows-msvc
Thread model: posix
InstalledDir: C:\Program Files\LLVM\bin

Ubuntu के लिए, LLVM निर्भरता को इस तरह इंस्टॉल किया जा सकता है. अन्य Linux डिस्ट्रिब्यूशन में 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) ...

जैसा कि ऊपर बताया गया है, 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 पैकेज के पुराने वर्शन हों. प्लग इन प्रोजेक्ट में 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

इस कॉन्फ़िगरेशन में LLVM को पास करने के लिए C हेडर फ़ाइल, जनरेट की जाने वाली आउटपुट फ़ाइल, फ़ाइल के सबसे ऊपर रखी जाने वाली जानकारी, और लिंट चेतावनी जोड़ने के लिए इस्तेमाल किया जाने वाला प्रीएंबल सेक्शन शामिल होता है.

फ़ाइल के अंत में एक कॉन्फ़िगरेशन आइटम है, जिसके लिए और जानकारी दी जानी चाहिए. ffigen के वर्शन 11.0.0 के बाद, अगर clang से हेडर फ़ाइलों को पार्स करते समय चेतावनियां या गड़बड़ियां जनरेट होती हैं, तो बाइंडिंग जनरेटर डिफ़ॉल्ट रूप से बाइंडिंग जनरेट नहीं करेगा.

जैसा कि लिखा गया है, Duktape हेडर फ़ाइलें, macOS पर clang को ट्रिगर करती हैं. इससे, Duktape के पॉइंटर पर खाली होने की जानकारी देने वाले एट्रिब्यूट की वैल्यू मौजूद नहीं होने की वजह से चेतावनियां जनरेट होती हैं. macOS और iOS Duktape के साथ काम करने के लिए, यह ज़रूरी है कि Duktape कोड बेस में यह टाइप तय करने की सुविधा मौजूद हो. इस दौरान, हम ignore-source-errors फ़्लैग को true पर सेट करके, इन चेतावनियों को अनदेखा करने का फ़ैसला ले रहे हैं.

प्रोडक्शन ऐप्लिकेशन में, ऐप्लिकेशन भेजने से पहले आपको कंपाइलर से जुड़ी सभी चेतावनियां हटानी चाहिए. हालांकि, Duktape के लिए ऐसा करना, कोडलैब (कोड बनाना सीखना) के दायरे से बाहर है.

अन्य कुंजियों और वैल्यू के बारे में ज़्यादा जानकारी के लिए, 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/

तकनीकी तौर पर, आपको ffigen के लिए सिर्फ़ duktape.h पर कॉपी करने की ज़रूरत है. हालांकि, आप CMake की कॉन्फ़िगर करने जा रहे हैं, ताकि एक ऐसी लाइब्रेरी बनाई जा सके जिसमें तीनों की ज़रूरत हो. नई बाइंडिंग जनरेट करने के लिए 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 को Windows, Linux, और macOS पर clang के साथ कंपाइल करने के लिए जाना जाता है.

C Maker को कॉन्फ़िगर करना

CMake एक बिल्ड सिस्टम जनरेशन सिस्टम है. यह प्लगिन, CMake की मदद से Android, Windows, और Linux के लिए बिल्ड सिस्टम जनरेट करता है. इससे, जनरेट की गई Flutter बाइनरी में Duktape को शामिल किया जा सकता है. टेंप्लेट से जनरेट की गई 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 का इस्तेमाल, यूनिक्स शैली की लाइब्रेरी की मदद करने के लिए किया गया है, जो Windows की दुनिया के लिए 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 '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;
}

यह फ़ाइल, डाइनैमिक लिंक लाइब्रेरी फ़ाइल (Linux और Android के लिए .so, Windows के लिए .dll) को लोड करने के लिए और एक ऐसा रैपर उपलब्ध कराती है जो मौजूदा C कोड में, ज़्यादा Dart मुहावरेदार इंटरफ़ेस दिखाता है.

यह फ़ाइल सीधे 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

आपको ऐप्लिकेशन इस तरह से चलता दिखेगा:

Windows ऐप्लिकेशन में शुरू किया गया Duktape दिखाया जा रहा है

Windows ऐप्लिकेशन में Duktape JavaScript आउटपुट दिखाना

ये दो स्क्रीनशॉट, JavaScript चलाएं बटन दबाने से पहले और बाद का तरीका दिखाते हैं. यह Dart से JavaScript कोड को एक्ज़ीक्यूट करने और उसके नतीजे को स्क्रीन पर दिखाने के बारे में बताता है.

Android

Android एक Linux, कर्नेल-आधारित OS है और कुछ हद तक डेस्कटॉप Linux डिस्ट्रिब्यूशन की तरह है. CMake का बिल्ड सिस्टम दोनों प्लैटफ़ॉर्म के बीच के ज़्यादातर अंतरों को छिपा सकता है. Android पर ऐप्लिकेशन बनाने और चलाने के लिए, पक्का करें कि Android एम्युलेटर चल रहा हो या Android डिवाइस कनेक्ट हो. ऐप्लिकेशन चलाएं. जैसे:

$ cd example
$ flutter run -d emulator-5554

अब आपको Android पर चलने वाला उदाहरण ऐप्लिकेशन दिखना चाहिए:

किसी Android एम्युलेटर में शुरू किया गया Duktape दिखाया जा रहा है

किसी Android एम्युलेटर में Duktape JavaScript आउटपुट दिखाया जा रहा है

6. macOS और iOS पर Duktape का इस्तेमाल करना

अब आपके प्लग इन को macOS और iOS पर काम करने का समय आ गया है. ये दोनों ऑपरेटिंग सिस्टम एक-दूसरे से जुड़े हुए हैं. macOS के साथ शुरू करें. CMake की सुविधा macOS और iOS के साथ काम करती है, लेकिन आप वह काम दोबारा इस्तेमाल नहीं करेंगे जो आपने Linux और Android पर, macOS और iOS पर Flutter में लाइब्रेरी इंपोर्ट करने के लिए CocoaPods का इस्तेमाल किया जाता है.

सफ़ाई करना

पिछले चरण में आपने Android, Windows, और Linux के लिए काम करने वाला ऐप्लिकेशन बनाया था. हालांकि, ओरिजनल टेंप्लेट में कुछ फ़ाइलें बची हैं. अब आपको उन्हें हटाना होगा. उन्हें अब नीचे बताए गए तरीके से हटाएं.

$ rm src/ffigen_app.c
$ rm src/ffigen_app.h
$ rm ios/Classes/ffigen_app.c
$ rm macos/Classes/ffigen_app.c

macOS

macOS प्लैटफ़ॉर्म पर Flutter, C और C++ कोड को इंपोर्ट करने के लिए CocoaPods का इस्तेमाल करता है. इसका मतलब है कि इस पैकेज को CocoaPods के बिल्ड इन्फ़्रास्ट्रक्चर में इंटिग्रेट किया जाना ज़रूरी है. पिछले चरण में, CMake के साथ बनाए जा चुके C कोड का फिर से इस्तेमाल करने के लिए, आपको macOS प्लैटफ़ॉर्म रनर में एक फ़ॉरवर्ड फ़ाइल जोड़नी होगी.

macos/Classes/duktape.c

#include "../../src/duktape.c"

यह फ़ाइल, आपके पिछले चरण में सेट अप किए गए नेटिव सोर्स कोड से सोर्स कोड को शामिल करने के लिए, C प्रीप्रोसेसर की पावर का इस्तेमाल करती है. यह कैसे काम करता है, इस बारे में ज़्यादा जानकारी के लिए macos/ffigen_app.podspec देखें.

इस ऐप्लिकेशन को चलाने से अब आप Windows और Linux पर देखे गए पैटर्न के अनुसार काम करेंगे.

$ cd example
$ flutter run -d macos

macOS ऐप्लिकेशन में शुरू किया गया Duktape दिखाया जा रहा है

macOS ऐप्लिकेशन में Duktape JavaScript आउटपुट दिखाया जा रहा है

iOS

macOS के सेटअप की तरह ही, iOS में भी एक फ़ॉरवर्ड करने वाली सी फ़ाइल जोड़ी जानी चाहिए.

ios/Classes/duktape.c

#include "../../src/duktape.c"

इस फ़ाइल के साथ, आपका प्लग इन अब iOS पर चलने के लिए भी कॉन्फ़िगर हो गया है. इसे सामान्य तरीके से चलाएं.

$ flutter run -d iPhone

iOS सिम्युलेटर में शुरू किया गया Duktape दिखाया जा रहा है

iOS सिम्युलेटर में Duktape JavaScript आउटपुट दिखाया जा रहा है

बधाई हो! आपने पांच प्लैटफ़ॉर्म पर नेटिव कोड को इंटिग्रेट कर लिया है. यह जश्न मनाने का आधार है! यह एक ज़्यादा फ़ंक्शनल यूज़र इंटरफ़ेस भी हो सकता है, जिसे अगले चरण में बनाया जा सकता है.

7. रीड इवल प्रिंट लूप को लागू करना

एक इंटरैक्टिव एनवायरमेंट में, किसी प्रोग्रामिंग भाषा के साथ इंटरैक्ट करना और भी ज़्यादा मज़ेदार हो जाता है. इस तरह के एनवायरमेंट को लागू करने का पहला तरीका, Lआईएसपी का रीड इवल प्रिंट लूप (आरईपीएल) था. इस चरण में, 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;
}

डाइनैमिक लिंक लाइब्रेरी को लोड करने वाले कोड का दायरा बढ़ाया गया है. इससे, टेस्ट रनर में प्लगिन के इस्तेमाल के मामले मैनेज किए जा सकेंगे. इसकी मदद से, ऐसे इंटिग्रेशन टेस्ट को चालू किया जा सकता है जो इस एपीआई का इस्तेमाल, Flutter टेस्ट के तौर पर करता है. JavaScript कोड की स्ट्रिंग का मूल्यांकन करने वाले कोड का इस्तेमाल इसलिए किया गया है, ताकि गड़बड़ी से जुड़ी स्थितियों को सही तरीके से हैंडल किया जा सके. उदाहरण के लिए, अधूरा या गलत कोड. यह अतिरिक्त कोड ऐसी स्थितियों को मैनेज करने का तरीका बताता है जहां स्ट्रिंग को बाइट अरे के रूप में लौटाया जाता है और उन्हें Dart स्ट्रिंग में बदलने की ज़रूरत होती है.

पैकेज जोड़ना

REPL बनाते समय, आप उपयोगकर्ता और Duktape JavaScript इंजन के बीच का इंटरैक्शन दिखाएंगे. जब कोई व्यक्ति कोड की लाइनें डालता है, तब Duktape जवाब में कंप्यूटेशन (हिसाब लगाना) के नतीजे या किसी अपवाद की वजह से जवाब देता है. आपको बॉयलरप्लेट कोड को लिखने की ज़रूरत को कम करने के लिए, freezed का इस्तेमाल करना होगा. आप दिखाए गए कॉन्टेंट को थीम के हिसाब से बनाने के लिए google_fonts और राज्य के मैनेजमेंट के लिए flutter_riverpod का भी इस्तेमाल करेंगे.

उदाहरण के तौर पर दिए गए ऐप्लिकेशन में, ज़रूरी डिपेंडेंसी जोड़ें:

$ 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 में दिखाई गई हर लाइन के आकार को तीन में से एक के तौर पर आसानी से दिखाया जा सके. इस समय, आपका कोड शायद इस कोड पर किसी तरह की गड़बड़ी दिखा रहा है, क्योंकि एक और कोड जनरेट करने की ज़रूरत है. इसे अब नीचे बताए गए तरीके से करें.

$ 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,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

इस कोड में बहुत कुछ है, लेकिन इन सभी चीज़ों को समझाना इस कोडलैब के दायरे से बाहर है. मेरा सुझाव है कि आप कोड चलाएं और उचित दस्तावेज़ों की समीक्षा करने के बाद कोड में बदलाव करें.

$ cd example
$ flutter run

Linux ऐप्लिकेशन में चल रहा Duktape REPL

Windows ऐप्लिकेशन में चल रहा Duktape REPL

iOS सिम्युलेटर में चल रहा Duktape REPL

Android एम्युलेटर में चल रहा Duktape REPL

8. बधाई हो

बधाई हो! आपने Windows, macOS, Linux, Android, और iOS के लिए, Flutter FFI पर आधारित प्लगिन बना लिया है!

प्लगिन बनाने के बाद, हो सकता है कि आप उसे ऑनलाइन शेयर करना चाहें, ताकि दूसरे लोग उसका इस्तेमाल कर सकें. अपने प्लग इन को pub.dev पर पब्लिश करने से जुड़े पूरे दस्तावेज़ पाने के लिए, प्लगिन पैकेज डेवलप करना पर जाएं.