Question #31MediumFlutter Basics

What are packages and plugins in Flutter?

#flutter

Answer

Overview

Packages and Plugins are reusable code libraries in Flutter. The key difference is that packages contain pure Dart code, while plugins include platform-specific native code (Android/iOS).


Comparison Table

FeaturePackagePlugin
CodePure Dart onlyDart + Native (Java/Kotlin/Swift/Objective-C)
Platform Access❌ No✅ Yes
Examplesprovider, http, intlcamera, battery, location
ComplexitySimpleComplex
Platform ChannelsNot usedUses MethodChannel/EventChannel
DependenciesDart onlyDart + Android/iOS SDKs
Use CaseBusiness logic, UIDevice features

Packages

What is a Package?

A package contains pure Dart code that runs on any platform.

Examples

  • provider - State management
  • http - HTTP networking
  • intl - Internationalization
  • uuid - UUID generation
  • equatable - Value equality

Installation

yaml
dependencies:
  provider: ^6.0.5
  http: ^1.1.0
  intl: ^0.18.0

Usage Example

dart
import 'package:http/http.dart' as http;
import 'package:provider/provider.dart';

// Pure Dart code - works everywhere
Future<void> fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com'));
  print(response.body);
}

// State management with provider
class Counter with ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();
  }
}

Plugins

What is a Plugin?

A plugin includes Dart code + platform-specific native code to access device features.

Examples

  • camera - Camera access
  • battery - Battery info
  • location - GPS location
  • shared_preferences - Local storage
  • url_launcher - Open URLs
  • image_picker - Pick images

Installation

yaml
dependencies:
  camera: ^0.10.5
  battery_plus: ^4.0.0
  geolocator: ^10.0.0
  shared_preferences: ^2.2.0

Usage Example

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

// Access battery (native Android/iOS code)
Future<int> getBatteryLevel() async {
  final battery = Battery();
  return await battery.batteryLevel;
}

// Access GPS (native code)
Future<Position> getCurrentLocation() async {
  return await Geolocator.getCurrentPosition();
}

// Local storage (native SharedPreferences/UserDefaults)
Future<void> saveData(String key, String value) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString(key, value);
}

How Plugins Work

Architecture

text
┌────────────────────────────────────────┐
│  Flutter App (Dart)                    │
│  ↓                                     │
│  Plugin Dart Code                      │
│  ↓                                     │
│  Platform Channel (MethodChannel)     │
└────────────────────────────────────────┘
         ↓                    ↓
┌──────────────────┐  ┌──────────────────┐
│  Android         │  │  iOS             │
│  (Kotlin/Java)   │  │  (Swift/Obj-C)   │
│  ↓               │  │  ↓               │
│  Native APIs     │  │  Native APIs     │
└──────────────────┘  └──────────────────┘

Platform Channel Example

Dart side:

dart
import 'package:flutter/services.dart';

class BatteryPlugin {
  static const platform = MethodChannel('com.example/battery');
  
  Future<int> getBatteryLevel() async {
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      return result;
    } catch (e) {
      return -1;
    }
  }
}

Android side (Kotlin):

kotlin
class MainActivity: FlutterActivity() {
  private val CHANNEL = "com.example/battery"
  
  override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
      .setMethodCallHandler { call, result ->
        if (call.method == "getBatteryLevel") {
          val batteryLevel = getBatteryLevel()
          result.success(batteryLevel)
        }
      }
  }
  
  private fun getBatteryLevel(): Int {
    val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
    return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
  }
}

iOS side (Swift):

swift
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(_ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    
    let controller = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "com.example/battery",
                                              binaryMessenger: controller.binaryMessenger)
    
    batteryChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      if call.method == "getBatteryLevel" {
        self.getBatteryLevel(result: result)
      }
    })
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  
  private func getBatteryLevel(result: FlutterResult) {
    UIDevice.current.isBatteryMonitoringEnabled = true
    let batteryLevel = Int(UIDevice.current.batteryLevel * 100)
    result(batteryLevel)
  }
}

Finding Packages/Plugins

Pub.dev (Official Repository)

Visit pub.dev to browse packages.

Quality Indicators:

  • ✅ Pub Points (max 160)
  • ✅ Popularity score
  • ✅ Likes count
  • ✅ Verified publishers
  • ✅ Null safety support
  • ✅ Recent updates

Popular Packages

State Management:

  • provider
  • riverpod
  • flutter_bloc
  • get

Networking:

  • http
  • dio

Local Storage:

  • shared_preferences
  • hive
  • sqflite

UI/UX:

  • flutter_svg
  • cached_network_image
  • shimmer
  • lottie

Utils:

  • intl
  • uuid
  • path_provider
  • url_launcher

Creating Your Own Package

Create Package

bash
flutter create --template=package my_package

Package Structure

text
my_package/
├── lib/
│   ├── my_package.dart      # Main export file
│   └── src/
│       └── my_class.dart    # Implementation
├── test/
│   └── my_package_test.dart
├── pubspec.yaml
├── README.md
├── CHANGELOG.md
└── LICENSE

Example Package

lib/my_package.dart:

dart
library my_package;

export 'src/string_utils.dart';

lib/src/string_utils.dart:

dart
class StringUtils {
  static String capitalize(String text) {
    if (text.isEmpty) return text;
    return '${text[0].toUpperCase()}${text.substring(1)}';
  }
  
  static String reverse(String text) {
    return text.split('').reversed.join('');
  }
}

pubspec.yaml:

yaml
name: my_package
description: Useful string utilities
version: 1.0.0
homepage: https://github.com/user/my_package

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

Publish to Pub.dev

bash
# Dry run
flutter pub publish --dry-run

# Publish
flutter pub publish

Creating Your Own Plugin

Create Plugin

bash
flutter create --template=plugin my_plugin

Plugin Structure

text
my_plugin/
├── lib/
│   └── my_plugin.dart         # Dart API
├── android/
│   └── src/main/kotlin/       # Android code
├── ios/
│   └── Classes/               # iOS code
├── example/
│   └── lib/main.dart          # Demo app
└── pubspec.yaml

Example Plugin

lib/my_plugin.dart:

dart
import 'package:flutter/services.dart';

class MyPlugin {
  static const MethodChannel _channel = MethodChannel('my_plugin');
  
  static Future<String> getPlatformVersion() async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}

android/src/main/kotlin/MyPlugin.kt:

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

ios/Classes/MyPlugin.swift:

swift
public class MyPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "my_plugin", binaryMessenger: registrar.messenger())
    let instance = MyPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }
  
  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    if call.method == "getPlatformVersion" {
      result("iOS " + UIDevice.current.systemVersion)
    } else {
      result(FlutterMethodNotImplemented)
    }
  }
}

Best Practices

Important: Always check package quality before using

✅ Choose Quality Packages

yaml
# Good - High pub points, recent update
dependencies:
  provider: ^6.0.5  # 160/160 points, 1M+ downloads

# Risky - Low quality indicators
dependencies:
  unknown_package: ^0.0.1  # 50/160 points, old version

Version Constraints

yaml
# ✅ Good - Allow compatible updates
dependencies:
  http: ^1.1.0

# ❌ Bad - Locked to exact version
dependencies:
  http: 1.1.0

Minimal Dependencies

yaml
# ✅ Good - Only what you need
dependencies:
  http: ^1.1.0

# ❌ Bad - Unused dependencies
dependencies:
  http: ^1.1.0
  dio: ^5.0.0  # Don't need both!

Resources