Sharing and Caching

  1. Objectives
  2. Codealong
  3. Boilerplate
  4. Making text selectable for copy and paste
  5. Sharing text
  6. Network Images and the CachedNetworkImage
  7. Image sharing
  8. Conclusion
  9. Other Resources

Objectives

Codealong

https://youtu.be/sEJ0obA2KzQ

Boilerplate

Create a new Flutter application. I called mine oversharing. Paste the following into main.dart:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: SharingScreen()));
}

class SharingScreen extends StatelessWidget {
  SharingScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: const Text("Sharing")),
        body: Padding(
          padding: const EdgeInsets.all(12),
          child: Center(
            child: Column(
              children: [
                Image.network(
                    "https://uncw.edu/media/images/image-text-feature/seahawk-statue-night-20171101-dsc3841-hdr-image-text-feature.jpg"),
                const SizedBox(
                  height: 16,
                ),
                const Text("601 S. College Rd, Wilmington, NC 28409")
              ],
            ),
          ),
        ));
  }
}

Your starting point should look like this:

Starting point with image and text

Making text selectable for copy and paste

Thus far, we have displayed text in simple Text widgets. You may have noticed that the user cannot select or copy the content of Text widgets.

A simple content sharing method is using the system’s clipboard to copy content and paste it elsewhere. To enable this for text in our app, we need to use the SelectableText widget. SelectableText has all of the styling functionality of a normal text widget, but with the capability to select the content and a context menu to copy it to the clipboard.

Quite literally, all you need to do is change the Text widget to a SelectableText widget to obtain the functionality. Now I can, for example, copy the address and paste it into Google Maps:

Why not use SelectableText widgets all the time? They are more expensive to create and destroy from a performance perspective. Plus, accidentally tapping on SelectableText will pop-in the selection cursors and obscure content, thus removing control from the user. Only provide SelectableText where appropriate or desired by your users.

Sharing text

Instead of copy & paste, suppose we want to share a block of text with another app, say a text messaging app, email app, or web browser. In general, this process is called sharing and has different underlying mechanisms based on whether you are on iOS or Android (sharing fundamentally different on web or desktop). Under the hood, sharing in a mobile app entails:

  1. wrapping the data you want to share in a virtual envelope
  2. telling the operating system “here is the data and its type”
  3. letting the operating system figure out which apps the data can be shared with
  4. showing a pop-up for the user to selected the desired app destination for the data

That’s a lot of steps. Fortunately, there’s a Flutter library for that called share_plus that simplifies it. Do the following:

  1. Open pubspec.yaml and add share_plus: ^6.3.0 to the dependencies section.
  2. If you are using Android, open /android/app/build.gradle and change the line compileSdkVersion flutter.compileSdkVersion to compileSdkVersion 34.
  3. Add import 'package:share_plus/share_plus.dart'; to the top of main.dart.
  4. Stop and relaunch the application – hot reload will not be sufficient for the new library to work.

Now, we’re going to create a new custom widget so that we can access the displayed string (“601 S. College Rd…”) in multiple places.

  1. Add the following Stateless widget to the bottom of main.dart:
     class ShareableText extends StatelessWidget {
       const ShareableText(this.text, {super.key});
    
       final String text;
    
       @override
       Widget build(BuildContext context) {
         return SelectableText(
           text,
         );
       }
     }
    
  2. Replace the SelectedText widget with the new ShareableText widget in the SharingScreen:
     const ShareableText("601 S. College Rd, Wilmington, NC 28409")
    
  3. Your app should look the same and the address should still be selectable.
  4. The SelectableText widget inside our new ShareableText widget has an onTap property that is triggered whenever the user taps on the text. The user can still make a regular copy & paste selection by long-pressing on the text. Update the ShareableText widget’s build method to the following:
     @override
     Widget build(BuildContext context) {
       return SelectableText(
         text,
         onTap: () {
           Share.share(text);
         },
       );
     }
    

The Share.share() method is imported from the share_plus library we added. Tap on the text, and you will see a familiar sharing pop-up. The options will vary based on the apps installed on your device. The Share.share() method has an optional second parameter subject which will fill the subject line of an Email if the user chooses such an application.

So there you have it – a relatively simple way of sharing text content from your app with other apps on your device.

Network Images and the CachedNetworkImage

We may also want to share Images from our app, but before that…

Consider the Image.network() widget we are using to load the picture. This widget handily tries to download the image from the web and display it. This is convenient but is inefficient because, behind the scenes, the file is re-downloaded every time the ShareScreen is loaded. The user may be burning up their data plan. Furthermore, if you are dealing with a large library of images (like Instagram), you would prefer to keep those images locally on the device just to not have to wait for them to load.

This is a scenario that calls for caching. Generally speaking, a cache computing is memory where recently- or frequently-used data is kept. Caching algorithms, which decide which data to cache and for how long, are highly dependent on context. The image below illustrates the basic concept of caching:

Illustration of cachine remote resources

As conscientious developers who want to deliver the best app performance and not burn up our users’ data plans, we should cache images that we download from the network. Fortunately, the cached_network_image will help! It provides a a CachedNetworkImage widget to gracefully replace Image.network(...)

Let’s incorporate the cached_network_image library first:

  1. Open pubspec.yaml and ddd cached_network_image: ^3.2.3 to the dependencies.
  2. Add import 'package:cached_network_image/cached_network_image.dart'; to the top of main.dart
  3. Replace the Image.network(...) widget in the SharingScreen with
     CachedNetworkImage(
         imageUrl: "https://uncw.edu/media/images/image-text-feature/seahawk-statue-night-20171101-dsc3841-hdr-image-text-feature.jpg",
         placeholder: (context, url) => const CircularProgressIndicator(),
         errorWidget: (context, url, error) => const Icon(Icons.error),
     ),
    

We get a few extra properties with the CachedNetworkImage. The placeholder property provides a widget to shows while the image is being actively downloaded. such as a Text widget that says “Data loading…”. In this case, we show a CircularProgressIndicator. The errorWidget property returns a widget to display if the URL was bad or the site can’t be reached.

Notice how the CircularProgressIndicator displays briefly while the image is loaded:

The CircularProgressIndicator will only show when the app is first launched. Why? Because the image is cached (downloaded to disk) after the first download, and Flutter loads the image from the disk rather than retrieving it from the network. Much more efficient!

In general, you should prefer using CachedNetworkImage from the cached_network_image when you are loading and displaying images from the network.

Image sharing

Now, what if you want to share an image from your application, say via text message? The process is similar to sharing text, but with the added complexity that we have to know the location of the image file to share.

If the image file is an asset in a known location, such as assets/images/seahawk.jpg, that is not so hard. Look at the code snippet here and invoke the appropriate Share call from a GestureDetector or Button onTap.

But, if the image file is downloaded from the network (like in our example), we have to do more work. If you are using CachedNetworkImages, like you should be, the hard work of downloading the image and storing it to a local file on disk (the cache) is done for you. But, we need to retrieve the locally-cached file from disk in order to share it.

For that, we need another library, flutter_cache_manager, which is used by cached_network_image for the caching logic. You don’t need this if you only wanted to display images. But, we need this package to access the cached files because we need those files to share our images.

Open pubspec.yaml and add flutter_cache_manager: ^3.3.0 to the dependencies section.

In order to share our network images, we will first create create another custom StatelessWidget called ShareableImage to wrap the functionality we need.

  1. Add the following class to main.dart:
     class ShareableImage extends StatelessWidget {
       final String source;
    
       const ShareableImage(this.source, {super.key});
    
       @override
       Widget build(BuildContext context) {
         return GestureDetector(
           onLongPress: () {},
           child: CachedNetworkImage(
             imageUrl: source,
             placeholder: (context, url) => const CircularProgressIndicator(),
             errorWidget: (context, url, error) => const Icon(Icons.error),
           ),
         );
       }
     }
    
  2. Replace the CachedNetworkImage(...) in SharingScreen with ShareableImage( "https://uncw.edu/media/images/image-text-feature/seahawk-statue-night-20171101-dsc3841-hdr-image-text-feature.jpg"),
  3. Save the changes. Your app should look the same.

We have wrapped the CachedNetworkImage widget with a GestureDetector to detect when the user long-presses on the image. We have not yet specified what to do when that long-press occurs.

We will define a private method _shareFile that retrieves the image File from the cache, converts it to Flutter’s cross-platform XFile object, and finally uses the Share library to share the image with other apps:

  1. Add import 'package:flutter_cache_manager/flutter_cache_manager.dart'; to the top of main.dart
  2. Create the following _shareFile() function inside the ShareableImage class:
     _shareFile() async {
       final cache = DefaultCacheManager(); // Gives a Singleton instance
       final file = await cache.getSingleFile(source);
       XFile toShare = XFile(file.path);
    
       Share.shareXFiles([toShare]);
     }
    
  3. Change onLongPress in the GestureDetector to onLongPress: () => _shareFile(),

Now, when you long-press on the image, you should have some options to share that image. Again, your options for sharing will vary based on which apps are installed on the device.

Very nice! We now have an approach for sharing images we download to our app with other apps on our phone.

Conclusion

We have demonstrated how to enable copy&paste of text, sharing of text, and sharing of images from a flutter app. You can download the completed code here; you will need to add the libraries we used to pubspec.yaml.

Other Resources