FLUTTER: Flutter Plugins

Flutter plugins bridge your Dart code and platform-native capabilities like the camera, GPS, and local storage. Understanding how to find, add, use, and even create plugins is essential for building feature-rich Flutter applications that interact with the real device.

A Flutter plugin is a package that wraps native platform code — Kotlin/Java for Android and Swift/Objective-C for iOS — and exposes it through a consistent Dart API. Plugins are what allow Flutter apps to feel fully native while sharing a single codebase.


What Are Flutter Plugins?

Plugins bridge Flutter code and the underlying native platform code, giving you access to device features that cannot be implemented in pure Dart.

md
Dart Code (Flutter App)
         |
         |  Platform Channel
         v
+--------------------+         +--------------------+
|   Android Plugin   |         |    iOS Plugin      |
|  (Kotlin / Java)   |         | (Swift / ObjC)     |
+--------------------+         +--------------------+
         |                               |
    Android APIs                     iOS APIs
  (Camera, GPS, etc.)           (Camera, GPS, etc.)

Why use plugins:

  • Reuse community-maintained code for standard device features
  • Provide a consistent API across iOS and Android
  • Avoid writing separate native code for every platform yourself
  • Access a large ecosystem of packages on pub.dev

Common Flutter Plugins

PluginPurpose
httpHTTP requests and REST API calls
dioAdvanced HTTP client with interceptors
shared_preferencesPersistent key-value storage
firebase_coreFirebase SDK initialization
sqfliteSQLite database access
google_maps_flutterGoogle Maps integration
cameraDevice camera access
geolocatorGPS and location services
image_pickerGallery and camera image selection
url_launcherOpen URLs in the browser

How to Use Plugins

Using a plugin is a three-step process.

Step 1 — Find the Plugin

Browse pub.dev, the official Dart package repository. Review the package's:

  • Popularity score and likes
  • Null safety support
  • Platform compatibility (iOS, Android, Web)
  • Last published date and maintenance status

Step 2 — Add the Plugin

Add the package to your project using the flutter pub add command:

bash
flutter pub add url_launcher
flutter pub add http
flutter pub add camera

This automatically updates pubspec.yaml and runs flutter pub get.

Alternatively, add it manually to pubspec.yaml:

yaml
dependencies:
  flutter:
    sdk: flutter
  url_launcher: ^6.2.0
  http: ^1.1.0
  shared_preferences: ^2.2.0

Then run:

bash
flutter pub get

Step 3 — Use the Plugin

Import the package in your Dart file and use its API:

dart
import 'package:url_launcher/url_launcher.dart';

Future<void> _openWebsite() async {
  final Uri url = Uri.parse('https://flutter.dev');
  if (!await launchUrl(url)) {
    throw Exception('Could not launch $url');
  }
}


HTTP Plugin Example

A complete example fetching data from a REST API using the http package.

dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<dynamic>> fetchPosts() async {
  final response = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts'),
  );
  if (response.statusCode == 200) {
    return json.decode(response.body);
  } else {
    throw Exception('Failed to load posts: ${response.statusCode}');
  }
}

class PostList extends StatefulWidget {
  const PostList({super.key});

  @override
  State<PostList> createState() => _PostListState();
}

class _PostListState extends State<PostList> {
  late Future<List<dynamic>> futurePosts;

  @override
  void initState() {
    super.initState();
    futurePosts = fetchPosts();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const AppBar(title: Text('API Data')),
      body: FutureBuilder<List<dynamic>>(
        future: futurePosts,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final post = snapshot.data![index];
                return ListTile(
                  title: Text(post['title']),
                  subtitle: Text(post['body']),
                );
              },
            );
          }
          return const Center(child: Text('No data'));
        },
      ),
    );
  }
}


Shared Preferences Plugin

The shared_preferences plugin stores simple key-value data persistently on the device.

bash
flutter pub add shared_preferences

dart
import 'package:shared_preferences/shared_preferences.dart';

// Save a value
Future<void> saveUsername(String username) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('username', username);
}

// Load a value
Future<String> loadUsername() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString('username') ?? 'Guest';
}

// Delete a value
Future<void> clearUsername() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('username');
}


Native Features via Plugins

Several device capabilities require explicit plugin installation and permission configuration.

Camera

bash
flutter pub add camera

After adding, configure permissions:

  • Android — add to android/app/src/main/AndroidManifest.xml:
  • xml
    <uses-permission android:name="android.permission.CAMERA"/>

  • iOS — add to ios/Runner/Info.plist:
  • xml
    <key>NSCameraUsageDescription</key>
    <string>This app requires camera access to take photos.</string>


Creating Your Own Plugin

When no existing plugin meets your needs, create a custom one.

bash
flutter create --template=plugin my_custom_plugin

This generates a plugin project structure:

md
my_custom_plugin/
├── lib/
│   └── my_custom_plugin.dart    <-- Dart API
├── android/                     <-- Kotlin/Java implementation
├── ios/                         <-- Swift/Objective-C implementation
├── example/                     <-- Example app
└── pubspec.yaml

The Dart API communicates with native code through Platform Channels:

dart
// In the Dart plugin code
static const MethodChannel _channel = MethodChannel('my_custom_plugin');

static Future<String> getPlatformVersion() async {
  final version = await _channel.invokeMethod<String>('getPlatformVersion');
  return version ?? 'Unknown';
}

On the native side (Android Kotlin example):

kotlin
class MyCustomPlugin : FlutterPlugin, MethodCallHandler {
  override fun onMethodCall(call: MethodCall, result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }
}


Managing Plugin Compatibility

Version Constraints

Specify compatible version ranges in pubspec.yaml to avoid breaking changes:

yaml
dependencies:
  http: ">=1.0.0 <2.0.0"
  shared_preferences: ^2.2.0  # Compatible with >=2.2.0 <3.0.0

Dependency Conflicts

Flutter's dependency resolver automatically finds a compatible version. If it fails:

  1. Run flutter pub deps to view the dependency tree
  2. Identify the conflicting packages
  3. Override specific versions using dependency_overrides in pubspec.yaml

Multi-Platform Testing

Always test plugins on real devices or emulators for both iOS and Android. Platform-specific behavior can differ significantly even when the Dart API appears identical.

bash
# Run on specific device
flutter run -d <device_id>

# List all available devices
flutter devices