Flutter Sample Part 5 – Saving Favorites

(This is my small series for getting started with Flutter mobile SDK for iOS and Android. See here for Part 4, Part 3, Part 2, and Part 1 .)

Though the Flickr sample app now allows you to save and display favorite photos, they don’t get saved, meaning that when the app is restarted everything is reset. In this entry I’ve added a very basic class, FavoritesStorage, which is responsible for writing out the list of favorites to local storage and reading them back in.

A relatively simple way to do this is to serialize the list of favorite photos to JSON and then write this JSON string out to a text file. Likewise, reading in the favorites requires de-serializing the JSON into a list of FlickrPhoto objects. (This is taken from the Flutter sample Reading and writing to files)

class FavoritesStorage {

  Future get _localPath async {
    final directory = await getApplicationDocumentsDirectory();
    return directory.path;
  }

  Future get _localFile async {
    final path = await _localPath;
    return File('$path/flickrfavorites.dat');
  }

  Future writeFavorites(List favoritesList) async {
    try {
      final file = await _localFile;

      // Read the file
      String json =  jsonEncode(favoritesList);

      print("JSON writing to file: " + json);

      await file.writeAsString(json, mode: FileMode.write);

      return true;

    } catch (e) {
      print('error $e');
    }

    return false;
  }

  Future readFavorites() async {
    try {
      final file = await _localFile;

      // Read the file
      String jsonString = await file.readAsString();

      print("JSON reading from file: " + jsonString);

      Iterable jsonMap = jsonDecode(jsonString);

      List favs = jsonMap.map((parsedJson) => FlickrPhoto.fromJson(parsedJson)).toList();

      return favs;

    } catch (e) {
      print('error');
    }

    return List();
  }
}

In order to allow FlickrPhotos to be serialized to JSON using the jsonEncode method above we need to add a toJson method to the FlickrPhoto class (see the Flutter documentation Serializing JSON inside model classes)

class FlickrPhoto {

  ...

  Map<String, dynamic> toJson() =>
      <String, dynamic> {
        'id': this.id,
        'owner': this.owner,
        'secret': this.secret,
        'server': this.server,
        'farm': this.farm,
        'title': this.title,
        'ispublic': this.ispublic,
        'isfriend': this.isfriend,
        'isfamily': this.isfamily,
        'ownername': this.ownerName,
        'datetaken': this.dateTaken.toIso8601String(),
        'views': this.numViews.toString()
      };
}

Now that we have a class which can read and write the favorite data we can modify the loadFlickrData method in FlickrMainPageState to call readAllFavorites after the list of photos are requested

loadFlickrData() async {

    String requestUrl = "https://api.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=" +
        flickrKey +
        "&format=json&nojsoncallback=1&extras=owner_name,date_taken,tags,views";

    var cacheManager = await CacheManager.getInstance();
    var file = await cacheManager.getFile(requestUrl);

    String lines = await file.readAsString();

    List favs = await widget.favorites.readAllFavorites();

    setState(() {
      widget.favorites.favorites = favs;
      Map userMap = jsonDecode(lines);
      flickrData = FlickrResult.fromJson(userMap);
      resultsCount = flickrData.photos.photo.length;
    });
  }

I’ve also added a fix for FlickrFavorites because the previously used contains method doesn’t work (Flickr photo doesn’t implement the Comparable interface). Instead it now checks if the Id values of a photo are the same as any in the list.

class FlickrFavorites {

  FavoritesStorage storage = FavoritesStorage();
  List favorites = [];

  Future readAllFavorites() async {
    favorites = await storage.readFavorites();
    return favorites;
  }

  Future addFavorite(FlickrPhoto photo) async {
    if (!favorites.any((p) => p.id == photo.id)) {
      favorites.add(photo);

      await storage.writeFavorites(favorites);
    }
  }

  Future removeFavorite(FlickrPhoto photo) async {
    favorites.removeWhere((p) => p.id == photo.id);

    await storage.writeFavorites(favorites);
  }

  bool isFavorite(FlickrPhoto photo) {
    return favorites.any((p) => p.id == photo.id);
  }
}

And that’s it! The favorites are persisted when re-starting the app. One last addition was to modify the getItem call for the favorites GridView so that it navigates to the photo details page when tapped:

Widget getItem(int itemNumber) {

    FlickrPhoto photo = widget.favorites.favorites[itemNumber];
    String farm = photo.farm.toString();
    String secret = photo.secret;
    String server = photo.server;

    String photoUrl = "https://farm$farm.staticflickr.com/$server/${photo.id}_$secret.jpg";

    return GestureDetector(
      child: Card(
          elevation: 5.0,
          child: Hero(
              tag: photo.title,
              child: CachedNetworkImage(
                  placeholder: CircularProgressIndicator(),
                  imageUrl: photoUrl,
                  height: 240.0,
                  width: double.infinity,
                  fit: BoxFit.cover)
          )
      ),
      onTap: () {
        setState((){
          navigateToItem(context, photo);
        });
      },
    );
  }

(The screen recording below seems to display the owner badge behind the photo in the photo details page, but this bug does NOT appear when you run the app)

flickr7

Source and repository here:

https://bitbucket.org/bitsbobsetc/fluttersample/src/SavingFavorites/flutter_flickr/

4 thoughts on “Flutter Sample Part 5 – Saving Favorites

  1. Pingback: Flutter Sample Part 6 – Sharing and Linking | Development Bits and Bobs

  2. Pingback: Flutter Sample Part 7 – Full Screen Image | Development Bits and Bobs

  3. Pingback: Flutter Sample Part 8 – Pull to Refresh | Development Bits and Bobs

  4. Pingback: Flutter Sample Part 9 – Using the Bloc Pattern | Development Bits and Bobs

Leave a comment