Flutter - Introduction to Package
Flutter - Introduction to Packages
Section titled “Flutter - Introduction to Packages”Packages are the foundation of code sharing and organization in Dart and Flutter. They allow developers to bundle reusable libraries, modules, or plugins that can be easily incorporated into multiple projects.
What is a Package?
Section titled “What is a Package?”A Dart package is essentially a directory containing Dart code and a crucial pubspec.yaml file that defines its metadata (name, version, dependencies, etc.). It’s like a Dart application but typically doesn’t have a main() entry point unless it’s designed to be an executable.
Standard package structure conventions:
lib/: Contains the public code of the package. Code insidelib/can be imported by other packages/applications.lib/src/: Contains private implementation details. Code insidelib/src/should generally not be imported directly by consumers; instead, it should be exported from a file directly insidelib/.lib/<package_name>.dart: Often serves as the main entry point file for the package, exporting the primary public APIs.pubspec.yaml: The metadata file defining the package.README.md: Documentation explaining what the package does and how to use it.CHANGELOG.md: Records changes between package versions.LICENSE: Specifies the software license.example/: (Optional but recommended) Contains a small example application demonstrating how to use the package.
To use code from a package named my_package:
// Import the main library fileimport 'package:my_package/my_package.dart';
// Or import a specific file within lib/import 'package:my_package/utils/helpers.dart';Types of Packages
Section titled “Types of Packages”Packages can be broadly categorized based on their dependencies and purpose:
Dart Packages
Section titled “Dart Packages”Contain only pure Dart code, without Flutter dependencies. They can be used in any Dart project (Flutter apps, command-line tools, server-side applications). Examples: http, intl, collection.
Flutter Packages
Section titled “Flutter Packages”Depend on the Flutter framework (sdk: flutter in pubspec.yaml) and contain Dart code specifically for Flutter applications, often providing custom widgets or Flutter-specific utilities. They do not contain platform-specific (Android/iOS) native code. Examples: provider, cached_network_image, fl_chart.
Flutter Plugins
Section titled “Flutter Plugins”A special type of Flutter package that includes both Dart code and platform-specific native code (Kotlin/Java for Android, Swift/Objective-C for iOS). Plugins are used to access platform features not available through Dart alone (e.g., camera, GPS, device sensors, native SDKs). They use Platform Channels internally. Examples: url_launcher, camera, shared_preferences, google_maps_flutter.
Using Packages
Section titled “Using Packages”Integrating third-party packages into your Flutter project is straightforward:
- Find Packages: Discover packages on the official repository: pub.dev.
- Add Dependency: Open your project’s
pubspec.yamlfile and add the package under thedependencies:section, specifying the package name and desired version constraint (e.g.,^1.2.3allows versions >= 1.2.3 and < 2.0.0).
dependencies: flutter: sdk: flutter # Add packages here http: ^1.1.0 provider: ^6.1.1 intl: ^0.18.1- Install Package: Run
flutter pub getin your terminal from the project root, or use the ‘Pub get’ action provided by your IDE (Android Studio/VS Code often prompt automatically after savingpubspec.yaml). This downloads and links the package. - Import and Use: Import the necessary Dart files from the package in your code and start using its classes and functions.
import 'package:http/http.dart' as http; // Use 'as' for namespaceimport 'package:provider/provider.dart';
void main() { // Use functions/classes from packages http.get(...); ChangeNotifierProvider(...);}Developing a Flutter Plugin
Section titled “Developing a Flutter Plugin”Let’s create the my_browser plugin from the platform channel examples as a reusable Flutter Plugin.
- Create Plugin Project: Use the IDE or command line:
-
- IDE (Android Studio/VS Code):
File>New>New Flutter Project...> SelectFlutter Plugin. Fill in the name (my_browser), organization, and desired platforms.
- IDE (Android Studio/VS Code):
-
- Command Line:
flutter create --template=plugin --platforms=android,ios -a kotlin -i swift my_browser(Flags specify platforms and preferred native languages).
- Command Line:
- Implement Dart API (
lib/my_browser.dart): Define the public Dart interface for the plugin.
import 'dart:async';import 'package:flutter/services.dart';
class MyBrowser { // Use the plugin's name for the channel static const MethodChannel _channel = MethodChannel('my_browser');
// Optional: Example platform version method often included in template static Future<String?> get platformVersion async { final String? version = await _channel.invokeMethod('getPlatformVersion'); return version; }
// Define the method to open the browser Future<bool> openBrowser(String urlString) async { try { final bool? result = await _channel.invokeMethod('openBrowser', { 'url': urlString }); // Return the success status from native code, default to false on error/null return result ?? false; } on PlatformException catch (e) { print('Failed to open browser via plugin: ${e.message}'); return false; } catch (e) { print('An unexpected error occurred in openBrowser: $e'); return false; } }}- Implement Android Native Code (
android/src/main/kotlin/.../MyBrowserPlugin.kt): Implement the platform channel handler.
package com.example.my_browser // Use your plugin's package name
import androidx.annotation.NonNullimport io.flutter.embedding.engine.plugins.FlutterPluginimport io.flutter.plugin.common.MethodCallimport io.flutter.plugin.common.MethodChannelimport io.flutter.plugin.common.MethodChannel.MethodCallHandlerimport io.flutter.plugin.common.MethodChannel.Resultimport io.flutter.embedding.engine.plugins.activity.ActivityAwareimport io.flutter.embedding.engine.plugins.activity.ActivityPluginBindingimport android.app.Activityimport android.content.Intentimport android.net.Uriimport android.util.Log
/** MyBrowserPlugin */class MyBrowserPlugin: FlutterPlugin, MethodCallHandler, ActivityAware { /// The MethodChannel that will hold the communication between Flutter and native Android private lateinit var channel : MethodChannel private var activity: Activity? = null // Hold activity reference
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { // Use binaryMessenger from binding channel = MethodChannel(flutterPluginBinding.binaryMessenger, "my_browser") channel.setMethodCallHandler(this) }
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) }
// MethodCallHandler implementation override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else if (call.method == "openBrowser") { val url = call.argument<String>("url") if (url != null && activity != null) { val success = openBrowserInActivity(url) result.success(success) } else if (activity == null) { result.error("NO_ACTIVITY", "Cannot open browser without an activity.", null) } else { result.error("INVALID_ARGUMENT", "URL argument is missing or invalid.", null) } } else { result.notImplemented() } }
private fun openBrowserInActivity(url: String): Boolean { return try { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) activity?.startActivity(intent) true // Indicate success } catch (e: Exception) { Log.e("MyBrowserPlugin", "Error opening browser", e) false // Indicate failure } }
// ActivityAware implementation (to get context/activity) override fun onAttachedToActivity(binding: ActivityPluginBinding) { activity = binding.activity }
override fun onDetachedFromActivityForConfigChanges() { activity = null }
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { activity = binding.activity }
override fun onDetachedFromActivity() { activity = null }}- Implement iOS Native Code (
ios/Classes/SwiftMyBrowserPlugin.swift):
import Flutterimport UIKit
public class SwiftMyBrowserPlugin: NSObject, FlutterPlugin { public static func register(with registrar: FlutterPluginRegistrar) { // Use registrar.messenger() let channel = FlutterMethodChannel(name: "my_browser", binaryMessenger: registrar.messenger()) let instance = SwiftMyBrowserPlugin() registrar.addMethodCallDelegate(instance, channel: channel) }
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { if call.method == "getPlatformVersion" { result("iOS " + UIDevice.current.systemVersion) } else if call.method == "openBrowser" { if let args = call.arguments as? [String: Any], let urlString = args["url"] as? String { openBrowser(urlString: urlString, result: result) } else { result(FlutterError(code: "INVALID_ARGUMENT", message: "URL argument is missing or not a string", details: nil)) } } else { result(FlutterMethodNotImplemented) } }
private func openBrowser(urlString: String, result: @escaping FlutterResult) { guard let url = URL(string: urlString) else { result(FlutterError(code: "INVALID_URL", message: "Could not parse URL: \(urlString)", details: nil)) return }
if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:]) { (success) in result(success) } } else { result(FlutterError(code: "CANNOT_OPEN_URL", message: "Cannot open URL: \(urlString)", details: nil)) } }}- Test the Plugin: The generated plugin project includes an
example/directory. Modifyexample/lib/main.dartto import and use your plugin.
// In example/lib/main.dartimport 'package:flutter/material.dart';import 'dart:async';
import 'package:flutter/services.dart';import 'package:my_browser/my_browser.dart'; // Import the plugin
void main() { runApp(const MyApp());}
class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key);
@override State<MyApp> createState() => _MyAppState();}
class _MyAppState extends State<MyApp> { String _platformVersion = 'Unknown'; final _myBrowserPlugin = MyBrowser(); // Create instance bool _browserOpened = false;
@override void initState() { super.initState(); initPlatformState(); }
Future<void> initPlatformState() async { String platformVersion; try { platformVersion = await MyBrowser.platformVersion ?? 'Unknown platform version'; } on PlatformException { platformVersion = 'Failed to get platform version.'; } if (!mounted) return; setState(() { _platformVersion = platformVersion; }); }
Future<void> _callOpenBrowser() async { bool success = await _myBrowserPlugin.openBrowser('https://flutter.dev'); if (mounted) { // Check if widget is still in the tree setState(() { _browserOpened = success; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(success ? 'Browser open attempt successful.' : 'Browser open attempt failed.')), ); } }
@override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin Example App'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Running on: $_platformVersion\n'), ElevatedButton( onPressed: _callOpenBrowser, child: const Text('Open Browser (flutter.dev)'), ), if (_browserOpened) const Text('Browser opened! (Check device)'), ], ) ), ), ); }}- Run the example app (
cd example,flutter run) on both Android and iOS simulators/devices to verify functionality. - Publish (Optional): Once tested, you can publish your plugin to pub.dev for others to use.
Developing plugins allows extending Flutter’s capabilities by bridging Dart with the power of native platform APIs.