If you’re an avid smartphone user, odds are you’ve seen something on a mobile application that you wanted to share with a friend, for example, while browsing through your favorite social media platform or exploring items for sale.
Giving your users the ability to share content from your app to other applications improves the overall user experience. In this article, you’ll learn how to implement content sharing in Flutter applications using the Share Plus plugin. To follow along with this article, you can check out the complete source code for the sample app. Let’s get started!
In this tutorial, we’ll cover how to share texts and images from our sample application to other mobile applications. We’ll fetch a list of fish species from the FishWatch API, display it in our app, then add the share functionality, enabling us to share information like the image, name, habitat, and quota of each fish specie with any other application. Let’s dive right in!
Create a new codebase by running the command below:
flutter create flutter_share
The command above would create the base files that we’ll build upon for our sample application. Next, we’ll import the following dependencies in our pubspec.yaml
file:
http
: To make a GET
request to the FishWatch API and retrieve the list of various fish speciesstacked
: An architectural solution that uses Provider under the hood, giving us access to classes that will spice up our development processbuild_runner
: Provides access to run commands for auto-generating files from annotationsstacked_generator
: Generates files from stacked annotationslogger
: Prints important information to the debug consoleshare_plus
: The package we’ll use to implement the share functionalitypermission_handler
: Enables us to request permission to use the phone’s mobile storageimage_downloader
: Enables us download the image file we want to sharedependencies: cupertinoicons: ^1.0.2 flutter: sdk: flutter http: ^0.13.4 imagedownloader: ^0.31.0 logger: ^1.1.0 permissionhandler: ^8.3.0 shareplus: ^3.0.4 stacked: ^2.2.8 devdependencies: buildrunner: ^2.1.7 flutterlints: ^1.0.0 fluttertest: sdk: flutter stacked_generator: ^0.5.7
Next, we’ll set up the models for converting the data we receive. From the FishWatch API documentation, we see that the API returns the name, description, image, and a host of other details. However, we only need the name, image, protein content, and quota.
Create a folder named models
in the lib
directory, which will hold all the models we’ll use. Create a file named fish_response_model.dart
where we’ll create the FishResponseModel
class and the ImageGallery
model class:
class FishResponseModel { String? speciesName; String? protein; ImageGallery? speciesIllustrationPhoto; String? quote; FishResponseModel({ required this.protein, required this.speciesIllustrationPhoto, required this.quote, }); FishResponseModel.fromJson(Map<String, dynamic> json) { speciesName = json['Species Name']; protein = json['Protein']; quote = json['Quote']; speciesIllustrationPhoto = ImageGallery.fromJson(json['Species Illustration Photo']); } } class ImageGallery { String? src; String? alt; String? title; ImageGallery({this.src, this.alt, this.title}); ImageGallery.fromJson(Map<String, dynamic> json) { src = json['src']; alt = json['alt']; title = json['title']; } }
With this done, we can proceed to register the dependencies that we’ll use in the application.
We’ll register the services that will perform tasks in our application as dependencies. We’ll create the basic structure of each of the services in the section, then flesh them out in subsequent sections.
Create a folder called services
in the lib
directory, which will hold the services that we’ll use in the app. The ApiService
handles all outbound connections from the application. Create a new file called api_service.dart
. For now, we’ll only add the basic structure:
class ApiService {}
The FishSpeciesService
service class will handle interactions with the FishWatch API. It will fetch the list of fish species and pass it into the app. Create a new file called fish_service.dart
and put the basic class structure there:
class FishSpecieService {}
The ShareService
service class will handle the share functionality, making it reusable in any part of the application. For the other services, create a new file called share_service.dart
and place the basic class structure as follows:
class ShareService{}
Next, we’ll set up our routes and register the services. We’ll use the @StackedApp
annotation, which comes from the Stacked package. The @StackedApp
annotation grants us access to two parameters, routes and dependencies. We’ll use the dependencies block to register our services.
Create a folder called app
in the lib
directory, which will hold all the configuration details of our application. In this folder, create a new file named app.dart
. Create an empty class and mark it with the @StackedApp
annotation. Also pass in the StackedLogger
to the logger
parameter. Next, register the services as LazySingleton
in the dependencies block:
import 'package:flutter_share/services/api_service.dart'; import 'package:flutter_share/services/fish_species_service.dart'; import 'package:flutter_share/services/share_service.dart'; import 'package:stacked/stacked_annotations.dart'; @StackedApp( dependencies: [ LazySingleton(classType: ApiService), LazySingleton(classType: FishSpecieService), LazySingleton(classType: ShareService) ], logger: StackedLogger(), ) class AppSetup {}
Now, we can go ahead and run the Flutter command to generate all of the necessary files for the setup configuration:
flutter pub run build_runner build --delete-conflicting-outputs
Now that we’ve registered each of the dependencies, we can go ahead and fill them up starting with the ApiService
. This service would handle all outbound HTTP requests from our application. It would have methods for each request type, GET
, POST
, PUT
, PATCH
, and DELETE
. However, for this article, we’ll use a GET
request to make a call to the FishWatch API endpoint.
Import the HTTP package as http
. In the ApiService
class, create a new function, which will accept a URL to which we’ll point our request. Make the call to the URL using the HTTP package, then check if the statusCode
is 200
. If it’s true, we’ll return the decodedResponse
.
In addition, we wrap the entire call with a try...catch
block in order to catch any exceptions that might be thrown. That’s basically everything in our ApiService
file:
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; class ApiService { Future<dynamic> get(url) async { try { final response = await http.get(url); if (response.statusCode == 200) { return json.decode(response.body); } } on SocketException { rethrow; } on Exception catch (e) { throw Exception(e); } } }
Next, we create a class to handle the constants relating to the API call, which will hold things like the scheme, host URL, URIs, etc. In the lib
directory, create a new folder called utils
and a new file called api_constants.dart
:
class ApiConstants { static const scheme = 'https'; static const baseUrl = 'fishwatch.gov'; static const path = 'api/species'; static get getFishSpecies => Uri( scheme: scheme, host: baseUrl, path: path, ); }
After this, the FishSpecieService
, which makes the call to the remote API, gets the data and parses it using the models we created earlier:
import 'package:flutter_share/models/fish_response_model.dart'; import 'package:flutter_share/app/app.locator.dart'; import 'package:flutter_share/services/api_service.dart'; import 'package:flutter_share/utils/api_constants.dart'; class FishSpecieService { final _apiService = locator<ApiService>(); Future<List<FishResponseModel?>?> getFishSpecie() async { List<FishResponseModel?> fishSpeciesList = []; try { final response = await _apiService.get(ApiConstants.getFishSpecies); if (response != null) { for (var i = 0; i < 15; i++) { final model = FishResponseModel.fromJson(response[i]); fishSpeciesList.add(model); } return fishSpeciesList; } } catch (e) { rethrow; } } }
Finally, we create the ShareService
, which would expose a method to share data from our application called onShare
. It would take in the model of the particular fishSpecie
from the list to be shared.
First we use the permission_handler package to request PermissionStatus
of the storage permission. We need the local storage to save the image we want to share. We download the image, then share the actual image, not a link to the image.
In addition to this, in the AndroidManifest.xml
, add the permission to write to the external storage, i.e., to download an image to the application local storage:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.flutter_share"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> .... </manifest>
Next, we check if the status is denied. If it is, we request for the permission.
Since we want to share an image file, and we are receiving the link to the image for each fish specie from the FishWatch API, we would download the image using the ImageDownloader
package. We would call the downloadImage
method on it and pass in the image URL from our model. We assign the response to a variable imageId
, which will be the imageId
for the downloaded image.
Next, we check if the imageId
is null. If it is, we return and break out of the function. After confirming that the imageId
is not null, we use the findPath
method from ImageDownloader
to get the path to the saved image on the local storage. This would be the path to the actual image file on your mobile device.
Finally, we call the shareFiles
object from the share_plus
package and pass the path to the imageId
, as well as the quote as the subject, and a text showing the specie name and protein content:
import 'package:flutter_share/models/fish_response_model.dart'; import 'package:image_downloader/image_downloader.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:share_plus/share_plus.dart'; class ShareService { Future<void> onShare(FishResponseModel fishSpecie) async { var status = await Permission.storage.status; if (status.isDenied) { await Permission.storage.request(); } var imageId = await ImageDownloader.downloadImage( fishSpecie.speciesIllustrationPhoto!.src!); if (imageId == null) { return; } var path = await ImageDownloader.findPath(imageId); await Share.shareFiles( [path!], text: '''Specie Name: ${fishSpecie.speciesName}\n''' '''Protein Content: ${fishSpecie.protein}\n''', subject: fishSpecie.quote, ); } }
With that, we’re done setting up our ShareService
. We can use this service to implement the share functionality anywhere in our application.
Next, head over to the main.dart
file. In the main block, we would call the WidgetsFlutterBinding.ensureInitialized()
, then call the setUpLocator
function, which will set up the locator making all registered dependencies available to us throughout our application:
import 'package:flutter/material.dart'; import 'package:flutter_share/app/app.locator.dart'; import 'package:flutter_share/ui/views/home/home_view.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); setupLocator(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Material App', home: HomeView(), ); } }
At this stage, we’ve successfully laid the foundation for the share functionality. Now, we can go ahead and build the user interface and finally use the service.
Create a new folder called ui
in the lib
directory, which will contain the user interfaces in the application. We’ll only have one, the home_view
. Create a new folder named home_view
where we’ll store two files, home_view.dart
and home_viewmodel.dart
, which would hold the UI code and the logic for the UI, respectively.
In the home_viewmodel.dart
file, create a class named HomeViewModel
. It will have two functions, one to getFishSpecie
from the API using the FishSpecieService
and the other to share the selected fishSpecie
.
We’ll access the respective services using the locator, then use the services to fetch the respective data:
import 'package:flutter/material.dart'; import 'package:flutter_share/app/app.locator.dart'; import 'package:flutter_share/models/fish_response_model.dart'; import 'package:flutter_share/services/fish_species_service.dart'; import 'package:flutter_share/services/share_service.dart'; import 'package:stacked/stacked.dart'; class HomeViewModel extends BaseViewModel { final _fishSpecieService = locator<FishSpecieService>(); final _shareService = locator<ShareService>(); List<FishResponseModel?>? fishSpeciesList = []; Future<void> getFishSpecies() async { try { fishSpeciesList = await runBusyFuture( _fishSpecieService.getFishSpecie(), throwException: true, ); notifyListeners(); } on Exception catch (e) { debugPrint(e.toString()); } } Future<void> shareFishSeries(FishResponseModel fishSeries) async { try { await _shareService.onShare(fishSeries); } on Exception catch (e) { debugPrint(e.toString()); } } }
Finally, in the home_view.dart
file, create a stateless widget, which would be the base for this view. The HomeView
would display the list of fish species gotten and stored into the fishesSpeciesList
in the HomeViewModel
.
The most important piece of the HomeView
is the share button, which we’ll use to trigger the share functionality. It would simply call the shareFishSeries
function in the HomeViewModel
, passing to it the fishSpecieModel
of the specie that was selected:
Row( mainAxisAlignment: MainAxisAlignment.end, children: [ const Icon(Icons.share, size: 18), const SizedBox(width: 8), GestureDetector( onTap: () { homeViewModel!.shareFishSeries(fishSpecieModel!); }, child: const Text( 'Share', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), ) ], )
Check our the complete code for the HomeView
in this gist.
With this, our sample app is good to go! Let’s build and run the app to see as it all comes together:
Share Implementation
Uploaded by Adegoke David on 2022-02-15.
In this tutorial, we’ve successfully pulled data from an API, then shared details of that data with other applications on our phone. You can now share images and texts from your application with other applications. You can also feel free to build more on this application and customize it to your desired look and feel.
I hope you enjoyed this tutorial! Happy coding. If you have any questions, feel free to leave a comment or reach out to me on Twitter @Blazebrain or LinkedIn.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowReact Native’s New Architecture offers significant performance advantages. In this article, you’ll explore synchronous and asynchronous rendering in React Native through practical use cases.
Build scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.