Teman Isyarat (Indonesian for "Sign Friend") is an on-device, real-time Indonesian Sign Language (BISINDO) recognition app built with Flutter and Kotlin. It captures hand and pose landmarks via CameraX and MediaPipe, runs inference through a custom TFLite model, and displays predictions — all fully offline.
- Overview
- Features
- Architecture
- Project Structure
- Tech Stack
- Getting Started
- Building
- Project Ecosystem
- License
The app is the mobile endpoint of a three-part system:
lm/— MediaPipe landmark extraction pipeline from raw videomodel/— GRU-based neural network training & TFLite exportandroid/(this repo) — Flutter app wrapping the exported TFLite model for on-device inference
It recognizes 20 BISINDO vocabulary words (Central Java dialect) through live camera feed, without requiring internet connectivity.
| Category | Words |
|---|---|
| Pronouns | Aku, Kamu, Dia |
| Common | Salam, Terima Kasih, Maaf, Nama |
| Time | Hari Ini, Besok |
| Color | Merah, Kuning |
| Family | Ayah, Ibu |
| Count | Satu, Dua, Tiga |
| Other | Teman, Buku |
| Fruit | Apel, Pisang |
- Real-time camera translation — Live CameraX preview with on-screen prediction overlay
- On-device ML — All inference runs locally via TensorFlow Lite; no network required
- MediaPipe landmark tracking — Pose (9 upper-body keypoints) + hands (2 × 21 landmarks)
- Temporal smoothing — Majority-vote over a 10-frame history window for stable output
- Circular frame buffer — 125-frame native
FloatArraybuffer feeding the TFLite model - Camera switch — Toggle between front and rear cameras
- Translation history — Persisted locally via
history.txtusingpath_provider - Skeleton overlay — Live canvas rendering of detected hand and pose landmarks
┌────────────────────────────────────────────────────────────┐
│ Flutter / Dart (UI) │
│ ┌─────────┐ ┌──────────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Home │ │ Translate │ │ Artikel │ │Settings │ │
│ │ Page │ │ Page │ │ Page │ │ Page │ │
│ └─────────┘ └──────┬───────┘ └──────────┘ └─────────┘ │
│ │ MethodChannel │
├──────────────────────┼─────────────────────────────────────┤
│ Android PlatformView (Kotlin) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ CameraX (PreviewView) ◄──► MediaPipe Landmarker │ │
│ │ (Pose + Hand) │ │
│ │ │ │ │
│ │ ┌────────────────────────────────────┘ │ │
│ │ │ assembleFrame() → 51 landmarks × 3 = 153-dim │ │
│ │ │ │ │
│ │ │ Circular Buffer (125 frames) ──► TFLite Interp. │ │
│ │ │ Softmax + Temporal Smooth. │ │
│ │ │ ──► MethodChannel callback │ │
│ │ └───────────────────────────────────────────────────┘ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
- CameraX frames are fed to MediaPipe Hand + Pose Landmarker
- 51 landmarks (9 pose + 21 left hand + 21 right hand) × 3 (x, y, z) are assembled per frame
- Landmarks are pushed into a circular 125-frame
FloatArraybuffer - When full, the buffer is sent to TFLite
Interpreter.run()with input shape[1, 125, 153] - Raw logits (20 classes) go through softmax → confidence threshold (0.7) → majority-vote temporal smoothing (10-frame window)
- The predicted label is sent back to Dart via
MethodChanneland rendered in the UI
temanisyarat/
├── lib/
│ ├── main.dart # App entry, MainPage, navigation, articles, settings
│ ├── pages/
│ │ └── translate/
│ │ ├── translate_page.dart # Camera + sign translation UI
│ │ ├── translate_controller.dart # Business logic, channel bridge, history
│ │ └── widgets/
│ │ └── scanning_dots.dart # Animated scanning indicator
│ └── services/
│ └── history_service.dart # Local file persistence for predictions
├── android/app/src/main/kotlin/com/example/android/
│ ├── handlandmarker/
│ │ ├── HandLandmarkerPlugin.kt # Flutter plugin registration (PlatformViewFactory)
│ │ ├── HandLandmarkerView.kt # PlatformView: CameraX + landmark buffer + TFLite
│ │ ├── HandLandmarkerHelper.kt # MediaPipe Hand/Pose Landmarker wrapper
│ │ └── HandLandmarkerOverlay.kt # Canvas skeleton overlay
│ └── MainActivity.kt # FlutterActivity entry
├── android/app/src/main/assets/models/
│ └── model_raw.tflite # Trained TFLite classification model
├── android/ # Android native project root
├── assets/icon/logo.png # App launcher icon
├── pubspec.yaml # Flutter dependencies & config
└── .tool-versions # Tool version pinning
| Component | Technology |
|---|---|
| Framework | Flutter 3.41 / Dart |
| State Management | StatefulWidget + setState |
| Navigation | Navigator.push / Bottom Navigation |
| Persistence | path_provider (file I/O) |
| Permissions | permission_handler (camera) |
| Component | Technology |
|---|---|
| Camera | CameraX (view, lifecycle, camera2) 1.4.2 |
| Landmark Detection | MediaPipe Tasks Vision 0.10.29 (Pose + Hand) |
| ML Runtime | TensorFlow Lite 2.14.0 + select TF ops |
| Platform Bridge | Flutter MethodChannel + PlatformView |
| Min SDK | 24 |
| Kotlin | 2.3.21 |
| Gradle | 9.5.1 |
| Property | Value |
|---|---|
| Input shape | [1, 125, 153] |
| Output shape | [1, 20] (logits) |
| Classes | 20 BISINDO words |
| Architecture | GRU + 1D Conv + TempAttention |
| Model size | ~2.6 MB (TFLite FP16) |
| Temporal smoothing | 10-frame majority vote |
| Confidence threshold | 0.7 |
- Flutter 3.41+ (see
.tool-versions) - JDK 17+ (OpenJDK 26 recommended)
- Android SDK (compileSdk from Flutter Gradle plugin)
- Android device or emulator (API 24+)
# Clone the repository
git clone https://github.com/temanisyarat/android.git
cd android
# Install Flutter dependencies
flutter pub get
# Run on connected device
flutter runThe TFLite model is bundled in android/app/src/main/assets/models/model_raw.tflite and is copied to the device cache directory on first launch.
flutter analyze # Static analysis
flutter test # Run widget tests# Debug APK
flutter build apk --debug
# Release APK
flutter build apk --release
# App Bundle
flutter build appbundle --release
# iOS (macOS only)
flutter build ios --releaseNote: Release signing currently uses the debug configuration. Configure a proper release keystore before publishing.
This app is part of the Teman Isyarat monorepo, which includes:
| Repository | Purpose |
|---|---|
lm/ |
MediaPipe landmark extraction pipeline — converts raw BISINDO videos to .npz landmark arrays |
model/ |
GRU-based neural network training, evaluation, and TFLite export |
android/ (you are here) |
Flutter + Kotlin Android app for on-device real-time recognition |
manager/ |
Obsidian vault with ADRs, specs, sprint tracking, and team documentation |
Video (raw MP4) ──► lm/ ──► .npz landmarks ──► model/ ──► .tflite ──► android/ ──► Live predictions
(MediaPipe extract) (Train GRU) (On-device inference)
This project is developed for academic purposes under the Hibah Jarprak program at Universitas Sebelas Maret (UNS), in partnership with GERKATIN Solo.
Built with Flutter, MediaPipe, and TensorFlow Lite for Indonesian Sign Language (BISINDO) recognition.