Flutter Mobile App Setup Guide
This guide covers setting up the Axionyx mobile application for iOS and Android development.
The Axionyx mobile app is built with:
- Flutter 3.2.6+ - Cross-platform UI framework
- Dart 3.2+ - Programming language
- Material Design 3 - UI design system
- Provider/Riverpod - State management (future)
Current Status: In early development
- Flutter SDK 3.2.6+ - Mobile development framework
- Git - Version control
- Code Editor - VS Code or Android Studio recommended
For Android Development:
- Android Studio
- Android SDK (API 26+)
- Java Development Kit (JDK) 11+
For iOS Development (macOS only):
- Xcode 14+
- CocoaPods
- iOS SDK
Linux:
# Download Flutter
cd ~
git clone https://github.com/flutter/flutter.git -b stable
# Add to PATH
echo 'export PATH="$PATH:$HOME/flutter/bin"' >> ~/.bashrc
source ~/.bashrc
# Verify installation
flutter doctormacOS:
# Using Homebrew (recommended)
brew install --cask flutter
# Or download manually
cd ~/development
unzip ~/Downloads/flutter_macos_*.zip
# Add to PATH
echo 'export PATH="$PATH:$HOME/development/flutter/bin"' >> ~/.zshrc
source ~/.zshrc
# Verify installation
flutter doctorWindows:
- Download Flutter SDK from https://docs.flutter.dev/get-started/install/windows
- Extract to
C:\flutter - Add
C:\flutter\binto PATH:- Search "Environment Variables" in Start menu
- Edit Path variable
- Add
C:\flutter\bin
- Restart terminal
- Run
flutter doctor
# Check installation and dependencies
flutter doctor
# Example output:
# [✓] Flutter (Channel stable, 3.16.0)
# [✓] Android toolchain - develop for Android devices
# [✓] Xcode - develop for iOS and macOS
# [✓] Chrome - develop for the web
# [✓] Android Studio (version 2023.1)
# [✓] VS Code (version 1.85)Android Studio (All Platforms):
- Download from https://developer.android.com/studio
- Run installer
- Open Android Studio
- Tools → SDK Manager
- Install:
- Android SDK Platform (API 34+)
- Android SDK Build-Tools
- Android Emulator
- Intel x86 Emulator Accelerator (HAXM) or equivalent
Accept Android Licenses:
flutter doctor --android-licenses
# Press 'y' to accept all licensesXcode (macOS only):
# Install Xcode from App Store
# Install Command Line Tools
sudo xcode-select --install
# Accept license
sudo xcodebuild -license accept
# Install CocoaPods
sudo gem install cocoapods
pod setupVS Code:
- Install Flutter extension
- Install Dart extension
Android Studio:
- Plugins → Flutter
- Plugins → Dart
git clone https://github.com/axionyx/axionyx.git
cd axionyx/mobile# Fetch all Flutter packages
flutter pub get
# Expected output:
# Resolving dependencies...
# Got dependencies!# Check for issues
flutter analyze
# Run tests
flutter testmobile/
├── lib/
│ ├── main.dart # App entry point
│ ├── app.dart # App widget
│ ├── config/ # Configuration
│ │ └── app_config.dart
│ ├── models/ # Data models
│ ├── services/ # API services
│ │ ├── api_service.dart
│ │ └── websocket_service.dart
│ ├── providers/ # State management (future)
│ ├── screens/ # App screens
│ │ ├── home_screen.dart
│ │ └── device_screen.dart
│ └── widgets/ # Reusable widgets
│ └── device_card.dart
├── test/ # Unit tests
├── android/ # Android-specific code
├── ios/ # iOS-specific code
├── web/ # Web support (optional)
├── pubspec.yaml # Dependencies
└── README.md
Android Emulator:
# List available emulators
flutter emulators
# Start emulator
flutter emulators --launch <emulator_id>
# Or via Android Studio:
# Tools → Device Manager → Play buttoniOS Simulator (macOS only):
# Start simulator
open -a Simulator
# Or from Xcode:
# Xcode → Open Developer Tool → Simulator# List connected devices
flutter devices
# Run on first available device
flutter run
# Run on specific device
flutter run -d <device_id>
# Run in debug mode (default)
flutter run --debug
# Run in profile mode
flutter run --profile
# Run in release mode
flutter run --releaseAndroid:
- Enable Developer Options on device
- Enable USB Debugging
- Connect via USB
- Run
flutter devicesto verify - Run
flutter run
iOS (macOS only):
- Connect iPhone/iPad via USB
- Trust computer on device
- Open
ios/Runner.xcworkspacein Xcode - Set Development Team in Signing & Capabilities
- Run
flutter run
While app is running:
- Hot Reload: Press
r(reloads code, preserves state) - Hot Restart: Press
R(full restart, clears state) - Quit: Press
q
Stateless Widget:
// lib/widgets/device_card.dart
import 'package:flutter/material.dart';
class DeviceCard extends StatelessWidget {
final String deviceName;
final String status;
final VoidCallback? onTap;
const DeviceCard({
Key? key,
required this.deviceName,
required this.status,
this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: ListTile(
title: Text(deviceName),
subtitle: Text('Status: $status'),
trailing: const Icon(Icons.arrow_forward_ios),
onTap: onTap,
),
);
}
}Stateful Widget:
// lib/screens/device_screen.dart
import 'package:flutter/material.dart';
class DeviceScreen extends StatefulWidget {
final String deviceId;
const DeviceScreen({Key? key, required this.deviceId}) : super(key: key);
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
double _temperature = 25.0;
bool _isRunning = false;
void _startDevice() {
setState(() {
_isRunning = true;
});
// Call API to start device
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Device ${widget.deviceId}'),
),
body: Column(
children: [
Text('Temperature: ${_temperature}°C'),
ElevatedButton(
onPressed: _isRunning ? null : _startDevice,
child: const Text('Start'),
),
],
),
);
}
}// Navigate to new screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DeviceScreen(deviceId: 'DUMMY-123'),
),
);
// Navigate and replace
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
// Go back
Navigator.pop(context);Create API Service:
// lib/services/api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final String baseUrl;
ApiService({required this.baseUrl});
Future<Map<String, dynamic>> getDeviceInfo() async {
final response = await http.get(
Uri.parse('$baseUrl/api/v1/device/info'),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to load device info');
}
}
Future<void> startDevice(double setpoint) async {
final response = await http.post(
Uri.parse('$baseUrl/api/v1/device/start'),
headers: {'Content-Type': 'application/json'},
body: json.encode({'setpoint': setpoint}),
);
if (response.statusCode != 200) {
throw Exception('Failed to start device');
}
}
}Use in Widget:
class DeviceScreen extends StatefulWidget {
@override
State<DeviceScreen> createState() => _DeviceScreenState();
}
class _DeviceScreenState extends State<DeviceScreen> {
final apiService = ApiService(baseUrl: 'http://192.168.4.1');
Map<String, dynamic>? deviceInfo;
bool isLoading = true;
@override
void initState() {
super.initState();
_loadDeviceInfo();
}
Future<void> _loadDeviceInfo() async {
try {
final info = await apiService.getDeviceInfo();
setState(() {
deviceInfo = info;
isLoading = false;
});
} catch (e) {
setState(() => isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
@override
Widget build(BuildContext context) {
if (isLoading) {
return const Center(child: CircularProgressIndicator());
}
return Scaffold(
appBar: AppBar(title: Text(deviceInfo?['name'] ?? 'Device')),
body: /* ... */,
);
}
}# Build debug APK
flutter build apk --debug
# Build release APK
flutter build apk --release
# Build split APKs per ABI (smaller sizes)
flutter build apk --split-per-abi
# Output: build/app/outputs/flutter-apk/app-release.apk# Build app bundle
flutter build appbundle --release
# Output: build/app/outputs/bundle/release/app-release.aab# Build for iOS
flutter build ipa --release
# Output: build/ios/iphoneos/Runner.app// test/services/api_service_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:axionyx/services/api_service.dart';
void main() {
group('ApiService', () {
test('getDeviceInfo returns device data', () async {
final apiService = ApiService(baseUrl: 'http://test.local');
// Add mock HTTP client
final info = await apiService.getDeviceInfo();
expect(info, isNotNull);
expect(info['id'], isNotEmpty);
});
});
}// test/widgets/device_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:axionyx/widgets/device_card.dart';
void main() {
testWidgets('DeviceCard displays device name', (tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: DeviceCard(
deviceName: 'Test Device',
status: 'Online',
),
),
),
);
expect(find.text('Test Device'), findsOneWidget);
expect(find.text('Status: Online'), findsOneWidget);
});
}// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:axionyx/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('full app test', (tester) async {
app.main();
await tester.pumpAndSettle();
// Verify home screen appears
expect(find.text('Axionyx'), findsOneWidget);
// Tap on device
await tester.tap(find.text('Device 1'));
await tester.pumpAndSettle();
// Verify device screen appears
expect(find.text('Temperature'), findsOneWidget);
});
}Run Tests:
# Unit tests
flutter test
# Integration tests
flutter test integration_test/Flutter Doctor Issues:
# Re-run doctor with verbose output
flutter doctor -v
# Common fixes:
flutter doctor --android-licenses # Accept licenses
sudo xcode-select -s /Applications/Xcode.app # Set Xcode pathBuild Failures:
# Clean project
flutter clean
# Get dependencies
flutter pub get
# Rebuild
flutter runGradle Errors (Android):
# Update Gradle wrapper
cd android
./gradlew wrapper --gradle-version=8.0
# Clean Gradle cache
./gradlew cleanCocoaPods Errors (iOS):
# Update CocoaPods
cd ios
pod repo update
pod install
# Clean derived data
rm -rf ~/Library/Developer/Xcode/DerivedData- Widgets: Keep widgets small and focused
- State Management: Use Provider or Riverpod for complex state
- Services: Separate API logic from UI
- Models: Define data models for type safety
// Use const constructors
const Text('Hello');
// Avoid rebuilds with const
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
}
// Use ListView.builder for long lists
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
);// Using Provider
class DeviceProvider extends ChangeNotifier {
List<Device> _devices = [];
List<Device> get devices => _devices;
Future<void> fetchDevices() async {
_devices = await apiService.getDevices();
notifyListeners();
}
}
// In widget
final deviceProvider = Provider.of<DeviceProvider>(context);- Build Screens: Implement device management UI
- Add Navigation: Set up routing between screens
- Integrate API: Connect to device/backend APIs
- Add State Management: Implement Provider/Riverpod
- Test on Devices: Test on physical iOS/Android devices