Build Wallpaper App Using Flutter And Riverpod - (Basic Functionality)

Introduction

A wallpaper app using flutter and Pexel API.

Requirements

  1. Install Flutter in Your System from flutter.dev
  2. Setup Your IDE (Android Studio or Vs Code ).
  3. Sign up at https://www.pexels.com and get your APIKEY.

Feature

  • Search Wallpaper
  • Download Wallpaper
  • Share Wallpaper Link
  • Set Wallpaper to Home and Lock Screen

Create A Flutter Project

Write this command in your terminal

flutter create <your project name>

Add these packages in your pubspec.ymal file,

cached_network_image: ^3.1.0
flutter_riverpod: ^0.14.0+3
http: ^0.13.3
image_gallery_saver: ^1.6.9
permission_handler: ^8.1.4+2
pexels_null_safety: ^0.1.2
pinch_zoom: ^1.0.0
random_string: ^2.3.1

Create a folder and files inside the lib directory

model -
Wallpaper_model.dart
page -
home_page.dart
search_page.dart
wallpaper_page.dart
wallpaper_preview_page.dart
provider -
wallpaper_provider.dart
repository -
wallpaper_repository.dart

Step 1

Get rid of all comments and MyHomePage widget in main.dart.

void main() async {
    runApp(
    MyApp(),
  );
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContextcontext) {
    return MaterialApp(
    title:'Wallpaper App',
     theme:ThemeData(
     primarySwatch:Colors.blue,
      ),
      home:HomePage(),
    );
  }
}

Step 2

Now create a Wallpaper Model class inside of the wallpaper_model.dart. And create WallpaperModel.fromJson method to convert API response to Wallpaper model class.

class WallpaperModel {
    WallpaperModel({
        this.page,
        this.perPage,
        this.photos,
        this.totalResults,
        this.nextPage,
        this.prevPage,
    });
    int ? page;
    int ? perPage;
    List < Photo > ? photos;
    int ? totalResults;
    String ? nextPage;
    String ? prevPage;
    factoryWallpaperModel.fromJson(Map < String, dynamic > json) => WallpaperModel(page: json["page"], perPage: json["per_page"], photos: List < Photo > .from(json["photos"].map((x) => Photo.fromJson(x))), totalResults: json["total_results"], nextPage: json["next_page"], prevPage: json["prev_page"], );
    Map < String, dynamic > toJson() => {
        "page": page,
        "per_page": perPage,
        "photos": List < dynamic > .from(photos!.map((x) => x.toJson())),
        "total_results": totalResults,
        "next_page": nextPage,
        "prev_page": prevPage,
    };
}

Step 3

In the lib directory create a new file key.dart to store your APIKEY.

class Api {
  static const String key =
      "your api key";
}

In wallpaper_repository.dart create a static getWallpaper function to get the list of wallpaper.

static Future < WallpaperModel ? > getWallpaper(int page) async {
    String url = 'https://api.pexels.com/v1/curated?page=$page&per_page=60';
    http.Response response = await http.get(Uri.parse(url), headers: {
        "Authorization": "${Api.key}",
        "content-type": "application/json; charset=utf-8",
    });
    try {
        if (response.statusCode == 200) {
            var jsonString = response.body;
            final wallpaper = wallpaperFromJson(jsonString);
            print('Length of wallpaper' + wallpaper.photos!.length.toString());
            return wallpaper;
        }
    } catch (e) {
        throw 'wallpaper not found';
    }
}

In the function, we will get a JSON response.

By using this wallpaperFromJson method to convert JSON object to wallpaper model class

final wallpaper = wallpaperFromJson(jsonString)

And return the wallpaper model response,

RiverPod

Riverpod is also a state management solution in a flutter. It has some advantages over a provider in that it is compile safe, does not have any limitation as a provider has, and does not also depend on flutter. Riverpod support multiple providers of the same type, combining asynchronous provider, adding providers from anywhere.

Step 4

In the wallpaper_proivider.dart we create a two provider pageNoProvider and wallpaperProvider.

final pageNoProvider =Provider((_) {
  Random random =Random();
  int value = random.nextInt(15);
  return value;
});

In pageNoProvider we get a random number between (0-15) which we will pass in

getWallpaper(int pageNo) function;

final wallpaperProvider =FutureProvider(
  (ref) =>Repository.getWallpaper(ref.read(pageNoProvider)),
);

FutureProvider is the equivalent of Provider but for asynchronous code.

We will assign the wallpaperProvider to getWallpaper function.

In main.dart wrap the MyApp Widget To ProviderScope .

void main() async {
  runApp(
  ProviderScope(child:MyApp()),
  );
}

For widgets to be able to read providers, we need to wrap the entire application in a "ProviderScope" widget.

This is where the state of our providers will be stored.

Step 5

In home_page.dart file we replaces StatelessWidget to ConsumerWidget .

Consumer Widget is identical in use to Stateless Widget, with the only difference being that it has an extra parameter on its build method, the “watch” object.

class HomePage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, watch) {
   final data =watch(wallpaperProvider);
     return Scaffold(
       body: data.when(
       data: (data) {
        return WallpaperPage(wallpaper:data!);
       },
      loading: () {
        return Center(
             child:CircularProgressIndicator(),
             );
            },
      error: (error, stackTrace) {
         return Center(
                 child:Text('${error.toString()}',
              ),
             );
            },
           ),
          );
         }
        }
final wallpaperdata = watch(wallpaperProvider);

watch is used inside the build method of a widget or inside the body of a provider to have the widget/provider listen to a provider.

wallpaperdata .when use to convert an AsyncValue into either a progress indicator, an error screen, or to show the data

On data success we return WallpaperPage Statelesswidget;

class WallpaperPage extends StatelessWidget {
    finalWallpaperModel wallpaper;
    constWallpaperPage({
        Key ? key,
        requiredthis.wallpaper,
    }): super(key: key);

Step 6

In wallpaper_page.dart.We define we create a SliverGrid to show two wallpaper card on the screen.

 

SliverGrid(delegate: SliverChildBuilderDelegate((context, int index) {
    final _wallpaper = wallpaper.photos ? [index];
    returnContainer(decoration: BoxDecoration(borderRadius: BorderRadius.circular(10.0), ), height: MediaQuery.of(context).size.height / 2, child: GestureDetector(onTap: () {
        //Wallpaper Preview
    }, child: Hero(tag: '${_wallpaper?.id}', child: ClipRRect(borderRadius: BorderRadius.circular(10.0), child: CachedNetworkImage(fit: BoxFit.cover, imageUrl: _wallpaper!.src!.large!, filterQuality: FilterQuality.high, progressIndicatorBuilder: (context, url, progress) => Center(child: CircularProgressIndicator()), ), ), ), ), );
}, childCount: wallpaper.photos?.length), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.5, crossAxisSpacing: 5.0 mainAxisSpacing: 5.0, ), ),

We Wrap the Scaffold To The RefreshIndicator Widget. RefreshIndicator gives us the Pull to Refresh Feature.

RefreshIndicator(onRefresh: () async {
            context.refresh(pageNoProvider);
            awaitcontext.refresh(wallpaperProvider);
        }, child: SiverGrid(

onRefresh function we calling refresh method. The provider being listened to may have been forced to refresh through the use of ref.refresh/context.refresh.

Inside of the SliverGrid we wrap the Container to GestureDetector Widget.

Flutter does provide common widgets handling tap, such as IconButton. But sometimes, you need to detect gestures on a custom view: here comes GestureDetector.

In GestureDetector onTap function is,

GestureDetector(onTap: () {
            Navigator.push(context, MaterialPageRoute(builder: (_) => WallpaperPrviewPage(photo: _wallpaper!), ), );
        }, child: Container(

To switch to a new route, use the Navigator.push() method. The push() method adds a Route to the stack of routes managed by the Navigator.

Step 8

In wallpaper_preview.dart.

We define the WallpaperPreviewPage StatefulWidget. The below code stack allows us to make a layer of widgets by putting them on top of each other. PinchZoom widget based on Flutter's new Interactive Viewer that makes picture pinch zoom, and return to its initial size and position when released.

Widget build(BuildContext context) {
    return Scaffold(key: scaffoldKey, body: Stack(children: [
        Positioned.fill(child: Hero(tag: '${widget.photo.id}', child: PinchZoom(maxScale: 3.5, onZoomStart: () {
            print('Start zooming');
        }, onZoomEnd: () {
            print('Stop zooming');
        }, child: CachedNetworkImage(placeholder: (_, value) {
            returnCenter(child: CircularProgressIndicator(), );
        }, imageUrl: widget.photo.src!.large2X!, filterQuality: FilterQuality.high, fit: BoxFit.cover, ), ), ), ),
    ], ), );
}

CachedNetworkImage Widget is used to load and cache network images. Can also be used with placeholder and error widgets. Positioned.fill widget is used to fit image on entire screen.

Get the full source code.

Summary

Flutter is an open-source UI software development kit created by Google. It is used to develop cross-platform applications for Android, iOS, Linux, macOS, Windows, and the web from a single codebase. In this part of the article, we learned the basics of the flutter and Riverpod, etc. In the next part of the article, we will cover search, download, share and set wallpaper to Home and LockScreen.