Almost Netflix: A Netflix clone built with Flutter + Appwrite
Tags: flutter, showdev, beginners, tutorial
Published: 2022-01-21 11:20:22

Welcome to the third post in our Almost Netflix series! We'll be building upon the [project setup from yesterday](https://dev.to/appwrite/did-we-just-build-a-netflix-clone-with-appwrite-28ok) and build a frontend for our Netflix Clone! In this post, we will take a closer look at building the clone using Flutter. In the subsequent posts of this series, we'll be building frontends for other platforms like iOS and Android!

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d246u7njx242bndti7dz.png) 

This one's all about Flutter, so let's get started!

It would be impossible to write every piece of code in this article 😬 You will read about all essential concepts, components, and communication with Appwrite. Still, if you want to check out every corner of our Almost Netflix in Flutter, you can check out the [GitHub Source Code](https://github.com/appwrite/demo-almost-netflix-for-flutter) that holds the whole application.

> **🤔 What is Appwrite?**
Appwrite is an open source backend-as-a-service that abstracts all the complexity involved in building a modern application by providing you with a set of REST APIs for your core backend needs. Appwrite handles user authentication and authorization, databases, file storage, cloud functions, webhooks, and much more! If anything is missing, you can extend Appwrite using your favorite backend language.

To run the project, checkout the source code and then `$ flutter run` and be sure to check out the screenshots:


## 📃 Requirements

Before we begin, we should have the Appwrite instance up and running, with the Almost Netflix project set up. Please follow our article about [Almost Netflix project setup](https://dev.to/appwrite/did-we-just-build-a-netflix-clone-with-appwrite-28ok) if you don't have a project prepared yet.

To build Almost Netflix we will be using Flutter along with a couple of libraries to help out.

To make things a bit easier to use and more reusable, we are going to use [provider](https://github.com/rrousselGit/provider) library instead of [InheritedWidget](https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html), including simplified resource management, reduced boilerplate, lazy loading and a nice listening mechanism to go with [ChangeNotifier](https://api.Flutter.dev/Flutter/foundation/ChangeNotifier-class.html)

To manage the user's preferences, location and credentials storage (across platform) we are going to lean on the [shared_preferences](https://pub.dev/packages/shared_preferences) library that will give us simplified persistent storage for simple data (NSUserDefaults on iOS/macOS, SharedPreferences on Android, etc.). To handle pop-ups and modals we are going to use [adaptive_dialog](https://pub.dev/packages/adaptive_dialog) for presenting our detail views as modals. For the pretty UI we are going to use as much "batteries included" UI (default is Material) as we can.

That's it, and if you are unfamiliar with these libraries or [Flutter](https://Flutter.dev) in general, please take some time to go read up! Never stop learning!

## 🛠️ Create Flutter Project
> If you do not have Flutter installed yet, please head over to [Flutter install](https://docs.Flutter.dev/get-started/install).

With Flutter installed, let's create our application. On the command line, simply run: `$ flutter create --platform=ios,android --org=io.appwrite /path/to/almost_netflix`. This command will create the app using the flag `--platform` to tell Flutter who you are targeting and `--org` for your company/personal identifier (usually it's a reverse url style deal). With our project created, let's open up that folder in our favorite editor.

In our project open up the file `pubspec.yaml` and lets add a few things, starting with our dependencies (mentioned above) first. Look for `dependencies:` and under it, let's add our libraries. Once you have added them, the dependencies section should look like:

```yaml

dependencies:
  Flutter:
    sdk: Flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  provider: ^6.0.1
  fluro: ^2.0.3
  appwrite: ^3.0.1
  shared_preferences: ^2.0.11

  # Any of your other dependencies too
```

The last item to take care of is our assets. We are going to create a new directory in our project called `assets` and then download (save) all our assets from [the static folder](https://github.com/appwrite/demo-almost-netflix-for-web/tree/master/static) on GitHub into our newly created `assets` folder. Now let's jump back into our `pubspec.yaml` file, and scroll till you see the section `assets:` and let's add our assets folder path so it looks like:

```yaml
# ...

Flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the Material Icons class.
  uses-material-design: true

  assets:
    - images/

#... contd.
```

Great, the project is ready! Now let's get Appwrite in here.

## 🤖 Appwrite Client

We are going to create `lib/api/client.dart` and write a simple singleton and some shortcuts for talking with our Appwrite backend.


```dart
// libs/api/client.dart

import 'package:appwrite/appwrite.dart';

class ApiClient {
  Client get _client {
    Client client = Client();

    client
    .setEndpoint('https://your.appwrite.url/v1')
    .setProject('yourNetflixClone')
    .setSelfSigned();
    
    return client;
  }

  static Account get account => Account(_instance._client);
  static Database get database => Database(_instance._client);
  static Storage get storage => Storage(_instance._client);

  static final ApiClient _instance = ApiClient._internal();
  ApiClient._internal();
  factory ApiClient() => _instance;
}
```

With our client in place we can now create **AccountProvider** that will manage the state and method calls for our user's account, such as sign-up and sign-in. All providers will be classes that extend `ChangeNotifier`

```dart
import 'dart:convert';
import 'package:Flutter/material.dart';
import 'package:appwrite/models.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:almostNetflix2/api/client.dart';

class AccountProvider extends ChangeNotifier {
  User? _current;
  User? get current => _current;

  Session? _session;
  Session? get session => _session;

  Future<Session?> get _cachedSession async {
    final prefs = await SharedPreferences.getInstance();
    final cached = cached.get("cached.session");

    if(cached == null) {
      return null;
    }
    
    return Session.fromMap(json.decode(cached));
  }

  Future<bool> isValid() async {
    if (session == null) {
      final cached = await _cachedSession;

      if (cached == null) {
        return false;
      }

      _session = cached;
    }

    return _session != null;
  }

  Future<void> register(String email, String password, String? name) async {
    try {
      final result = await ApiClient.account.create(
        userId: 'unique()',
        email: email, 
        password: password, 
        name: name
      );

      _current = result;

      notifyListeners();
    } catch(_e) {
      throw Exception("Failed to register");
    }
  }

  Future<void> login(String email, String password) async {
    try {
      final result = await ApiClient.account.createSession(email: email, password: password);
      _session = result;

      final prefs = await SharedPreferences.getInstance();
      prefs.set("cached.session", json.encode(result.toMap()));

      notifyListeners();
    } catch(e) {
      _session = null;
    }
  }
}
```

Now that we have our provider, and our little nice provider methods for our account, we need to tell the app we want to use providers, so let's head over to `main.dart` and make it "Provider Aware" updating it to the following:

```dart
// main.dart

import 'package:appflix/providers/account.dart';
import 'package:Flutter/material.dart';
import 'package:provider/provider.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => AccountProvider()),
      ],
      child: Main(),
    )
  );
}

class Main extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Appflix',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.black,
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: FutureBuilder(
        future: context.read<AccountProvider>().isValid(),
        builder: (context, snapshot) => Container(
          child: Text("Main")
        ),
      )
    );
  }
}
```

## 🔐 Authentication

For authentication we are going to create 2 forms for our onboarding screen. You can see the completed widget here: [onboarding.dart](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/onboarding.dart). Be sure to grab it and add it into your project. On both forms you will see that the submit buttons have `onPressed` events. With our provider we can use this event to sign-in or sign-up a user:

![Sign Up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/srpu5tso0eb7gektdaot.png)
 

[Sign Up:](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/onboarding.dart#L199)
```dart
  onPressed: () async {
    final api = context.read<AccountProvider>();
    final name = _nameController.text;
    final email = _emailController.text;
    final password = _passwordController.text;

    if (email.isEmpty || password.isEmpty) {
      showDialog(context: context, builder: (_) => AlertDialog(
        title: const Text('Error'),
        content: const Text('Please enter your email and password'),
        actions: [
          TextButton(
            child: const Text('OK'),
            onPressed: () => Navigator.of(context).pop(),
          )
        ],
      ));

      return;
    }
    
    await api.register(email, password, name);

  },
```

![Sign In](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yp7g33pixuseuxacfabi.png)
 
[Sign in:](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/onboarding.dart#L88)
```dart
onPressed: () async {
  final api = context.read<AccountProvider>();
  final email = _emailController.text;
  final password = _passwordController.text;

  if (email.isEmpty || password.isEmpty) {
    showDialog(context: context, builder: (_) => AlertDialog(
      title: const Text('Error'),
      content: const Text('Please enter your email and password'),
      actions: [
        TextButton(
          child: const Text('OK'),
          onPressed: () => Navigator.of(context).pop(),
        )
      ],
    ));

    return;
  }
  
  await api.login(email, password);

},
```

The sign-in/sign-up events take the entered email and password and then call their associated functions on `AccountProvider` that we added earlier, once complete our provider will notify everyone who is watching for a valid user to show up. So let's head back over to `main.dart` and make a quick adjustment to react when a user is present.

Update `main.dart` to:

```dart
class Main extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Appflix',
      theme: ThemeData(
        scaffoldBackgroundColor: Colors.black,
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: FutureBuilder(
        future: context.read<AccountProvider>().isValid(),
        builder: (context, snapshot) => context.watch<AccountProvider>().session == null ? const OnboardingScreen() : const Container(child: Text("HOME")),
      )
    );
  }
}
```

## 🎬 Movies page

![Movies](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrlkp8per1oouuchxuxp.png)
 
Once our user is authenticated we will need to load up the movies page that will show a featured movie followed by a couple of lists of movies. For this we will create our EntryProvider that will give us access to our Appwrite database of movies. So we are going to create 2 files: `lib/data/entry.dart` and `lib/providers/entry.dart`

First `lib/data/entry.dart` will be our Data model for our Movie data stored in Appwrite's database.

```dart
class Entry {
  final String id;
  final String name;
  final String? description;
  final String ageRestriction;
  final Duration durationMinutes;
  final String thumbnailImageId;
  final String genres;
  final String tags;
  final DateTime? netflixReleaseDate;
  final DateTime? releaseDate;
  final double trendingIndex;
  final bool isOriginal;
  final String cast;

//... cont'd
}
```

Second our `lib/providers/entry.dart`

```dart
class EntryProvider extends ChangeNotifier {
  
  static String _collectionId = "movies";

  Entry _featured = Entry.empty();
  Entry get featured => _featured;

  List<Entry> _entries = [];
  List<Entry> get entries => _entries;

  List<Entry> get originals => _entries.where((entry) => entry.isOriginal).toList();

  List<Entry> get trending {
    var trending = _entries;

    trending.sort((a, b) => b.trendingIndex.compareTo(a.trendingIndex));

    return trending;
  }

  Future<void> list() async {
    var result = await ApiClient.database.listDocuments(collectionId: _collectionId);

    _entries = result.documents.map((document) => Entry.fromJson(document.data)).toList();
    _featured = _entries.isEmpty ? Entry.empty() : _entries[0];

    notifyListeners();
  }

  Future<Uint8List> imageFor(Entry entry) async {
    return await ApiClient.storage.getFileView(fileId: entry.thumbnailImageId);
  }
}
```

Then we create our homepage, which you can view at [lib/screens/home.dart](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/home.dart) and attach our provider. Here we tell our widget to watch the `featured` property of our provider, if it changes the view will redraw for the updated property.

[Watch Featured](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/home.dart#L69)
```dart
slivers: [
  SliverToBoxAdapter(
    child: ContentHeader(featured: context.watch<EntryProvider>().featured),
  ),

```

Next we tell our home widget to also watch the list in the provider to provide data to display in our lists.

[Watch all entries](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/home.dart#L85)
```dart
SliverToBoxAdapter(
  child: ContentList(
    title: 'Mylist',
    contentList: context.watch<EntryProvider>().entries,
    isOriginal: false,
  ),
),
```

With that our app will now show a list view with several sub-lists which are segmented into categories.

## 🕵️ Detail page

![Movie Detail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wpwtl4einxftnzj45zr.png)

With our lists in place, we want a user to be able to tap a movie and get a modal to give more detail about the movie selected. On user select we are going to tell our provider that we have a selected entry, so let's add the following to our `lib/providers/entry.dart`

```dart
Entry? _selected;
Entry? get selected => selected;

Future<void> select(Entry? entry) async {
  _selected = entry;

  notifyListeners();
}
```

And now we need to create a new view called `lib/screens/details.dart` to display our selected entry as well as update `lib/widgets/content/list.dart` to present a modal of our movie's detail view.

Let's update `list.dart` to use [adaptive_dialog](https://pub.dev/packages/adaptive_dialog) and present our details view for the selected movie.

[Show the Modal](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/widgets/content/list.dart#L57)
```dart
onTap: () {
  await showDialog(
    context: context, 
    builder: (context) => const DetailsScreen(entry: current),
  );
},
```

Now we need to update `lib/screens/details.dart` to allow the selected entry (movie) to be passed in.

```dart
class DetailsScreen extends StatefulWidget {
  final Entry _entry;

  DetailsScreen({Key? key, required Entry entry}) : _entry = entry, super(key: key);
```

So now when a user taps a movie in the list, they will be presented with a detail view for their selection.

## 🔖 Watchlist page

![Watchlist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iidwb3ehazqavuo5cjp6.png)
 

We want our user to be able to list, add and remove movies from their own watchlist, so to do this we will create another provider `lib/providers/watchlist.dart` that will handle our api calls and also communicating to the UI the data and actions. So we need to tell the app that we want to use our `WatchlistProvider` by adding it to our `lib/main.dart` like we did with the others:

```dart
Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => AccountProvider()),
        ChangeNotifierProvider(create: (context) => EntryProvider()),
        ChangeNotifierProvider(create: (context) => WatchListProvider()),
      ],
      child: Main(),
    )
  );
}
```

When our user selects "My List" we will present a list of movies saved to the watchlist, with an option to delete it. 

In `libs/widgets/content/bar.dart` we are going to add an action to the `_AppBarButton`

[Show Watchlist](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/widgets/content/bar.dart#L37)
```dart
_AppBarButton('My List', () async {
  await showDialog(
    context: context, 
    builder: (context) => const WatchlistScreen()
  );
}),
```

Now when the user adds a movie to their watchlist, they can come here to see or delete it.

For our users to add a movie to their list, we call the `add` method on the `WatchlistProvider` and once added we dismiss the detail view.

[Add to Watchlist](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/details.dart#L87)
```dart
 tap: () {
  context.read<WatchListProvider>().add(widget._entry);
  Navigator.pop(context);
}
```

And to delete a movie from our list, we add a call to our `remove` method on our `WatchlistProvider`.

[Remove from Watchlist](https://github.com/appwrite/demo-almost-netflix-for-flutter/blob/main/lib/screens/watchlist.dart#L91)
```dart
tap: () {
  context.read<WatchListProvider>().remove(entry);
}
```

## 👨‍🎓 Conclusion

And with that, we have cloned (Almost) Netflix with nothing more than some frontend magic, and Appwrite as our backend! To become part of the Appwrite community, you can join our [Discord](https://appwrite.io/discord) server. I look forward to seeing what you build and who you are (when you join our discord!)

Now for the post credits scene: We have more to do, we have more to show, so as Appwrite releases, our Netflix clone will grow so get ready for anything, from streaming to dreaming! 😉


## 🔗 Learn more

You can use the following resources to learn more and get help regarding Appwrite and its services

- 🚀 [Almost Netflix Repo](https://github.com/appwrite/demo-almost-netflix-for-flutter)
- 🚀 [Appwrite Github](https://github.com/appwrite)
- 📜 [Appwrite Docs](https://appwrite.io/docs)
- 💬 [Discord Community](https://appwrite.io/discord)