HomeAbout

YouTube API - channel & playlist part 2 of 2

Published: Fri Feb 12 2021 17:00:00
Flutter 1.22.6

This is the second article of the YouTube API app. In this article will we continue build the app and display playable YouTube videos from each playlist.

In the first article we got the required API key in order to make use of the YouTube API. We also created a playlists screen, to display the playlists of a specified channel which we fetched using the YouTube API. In this article are we finishing the app by displaying playable videos from a playlist. You can find the complete app on GitHub

Add video model

Just like we're saving each playlist detail within a playlist model, are we saving each video detail in a video model. The only information we need to display a video is the video id, but we're also going to save the title to display below each video.

Within lib/models create a new file named video.dart and add the following code within.

1class Video {
2  final String id;
3  final String title;
4
5  Video({
6    this.id,
7    this.title
8  });
9
10  factory Video.fromJson(Map<String, dynamic> json) => Video(
11    id: json['snippet']['resourceId']['videoId'],
12    title: json['snippet']['title'],
13  );
14}

We will create a new object using the requested API data using the factory method factory Video.fromJson().

Add video support

We installed a YouTube video package in the first article. All there's left to do, in order to play videos, is to add the support in info.plist in iOS and build.gradlein Android.

iOS support embedded videos

Open ios/Runner/Info.plist and add following

1<key>io.flutter.embedded_views_preview</key>
2<true/>

Android support embedded videos

Set minSdkVersion of your android/app/build.gradle file to at least 17.

"Note: Although the minimum to be set is 17, the player won't play on device with API < 20. For API < 20 devices, you might want to forward the video to be played using YouTube app instead, using packages like url_launcher or android_intent."

Create videos screen

We need a new screen in which we'll list each video within a playlist. The app will navigate from Playlists (list of playlists) screen to the Videos screen (list of videos). The videos screen will present rows of playable videos from a specific playlist.

The packages we're imported is following (nothing new):

1import 'package:flutter/material.dart';
2import 'package:flutter_dotenv/flutter_dotenv.dart' as DotEnv;
3import 'package:http/http.dart' as http;
4import 'dart:convert' as convert;
5import '../models/playlist.dart';
6import '../models/video.dart';
7import '../widgets/youtube-video-row.dart';

The actual widget will be a stateful widget, since we're making an API request in order to get the videos (the state will change upon fetch).

1class Videos extends StatefulWidget {
2  @override
3  _VideosState createState() => _VideosState();
4}
5
6class _VideosState extends State<Videos> {
7  @override
8  Widget build(BuildContext context) {
9    return Container();
10  }
11}

A Playlist object will be passed to this widget upon navigating to it. Create a variable to hold the playlist variable and add it in the constructor of the widget

1class Videos extends StatefulWidget {
2  final Playlist playlist;
3
4  Videos({
5    Key key,
6    this.playlist,
7  }):super(key: key);
8
9  ...

Add a variable holding a list of videos in the class _VideosState and override the initState()method to make API call once navigated to the screen.

1class _VideosState extends State<Videos> {
2  List<Video> videos = [];
3
4  @override
5  void initState() {
6    super.initState();
7
8    fetchVideos();
9  }

Create the fetchVideos()method to make API call with the playlist ID as parameter and handle the response to update the List videos.

1Future<void> fetchVideos() async {
2  String url = 'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&key=${DotEnv.env['API_KEY']}&maxResults=50&playlistId=${widget.playlist.id}';
3
4  var response = await http.get(url);
5  if (response.statusCode == 200) {
6    var jsonResponse = convert.jsonDecode(response.body);
7    setState(() {
8      videos = jsonResponse['items'].map<Video>((item) {
9        return Video.fromJson(item);
10      }).toList();
11    });
12  } else {
13    print('I should handle this error better: ${response.statusCode}.');
14  }
15}

In the build method, use ListView.separated again to display the video rows (just like we did in the Playlists screen. Notice the AppBar in the Scaffold widget. It's a ready (and easy) to use dynamic app bar - perfect for most use cases. It'll per default show a back button for the user, so the user can go back to the Playlists screen. We have yet to create the YouTubeVideoRow.

1Widget build(BuildContext context) {
2  return Scaffold(
3    appBar: AppBar(
4      title: Text(widget.playlist.title),
5    ),
6    body: SafeArea(
7      child: ListView.separated(
8          separatorBuilder: (BuildContext context, int index) {
9            return SizedBox(height: 50);
10          },
11          itemCount: videos.length,
12          itemBuilder: (context, index) {
13            return YouTubeVideoRow(video: videos[index]);
14          }
15      ),
16    ),
17  );
18}

YouTubeVideoRow widget

This widget will change once an API request for the videos within the playlists is done. It'll be a StatefulWidget in order to handle this and update to show the videos once fetched.

1class YouTubeVideoRow extends StatefulWidget {
2  @override
3  _YouTubeVideoRowState createState() => _YouTubeVideoRowState();
4}
5
6class _YouTubeVideoRowState extends State<YouTubeVideoRow> {
7  @override
8  Widget build(BuildContext context) {
9    return Container();
10  }
11}

The widget will hold a Videoobject, since each video row will have just one video.

1class YouTubeVideoRow extends StatefulWidget {
2  final Video video;
3
4  const YouTubeVideoRow({
5    Key key,
6    this.video
7  }) : super(key: key);
8
9  ...

We're going to make use of YoutubePlayerController In the class _YouTubeVideoRowState which will take initial parameters for the video. It'll also give us the possibility to play or pause the video and other useful methods.

1class _YouTubeVideoRowState extends State<YouTubeVideoRow> {
2  YoutubePlayerController _controller;

Override the initState() method and initiate the YoutubePlayerController by passing the widget.video.id. You can add flags when initiating the controller, read more about them here. In this example we set autoPlay: false since we're loading 50 videos and don't wish them all to start playing once fetched. We also set mute: false since we leave it to the user to decide whether to play the video or not.

1@override
2void initState() {
3  super.initState();
4
5  _controller = YoutubePlayerController(
6    initialVideoId: widget.video.id,
7    flags: YoutubePlayerFlags(
8      autoPlay: false,
9      mute: false,
10    ),
11  );
12}

The build method will return a YoutubePlayerBuilder which takes YoutubePlayer as player parameter. The builder parameter will return a Column widget, containing the actual player and under a SizedBox for padding, a Container widget with Text widget as child, holding the actual video title.

1@override
2Widget build(BuildContext context) {
3  return YoutubePlayerBuilder(
4      player: YoutubePlayer(
5        controller: _controller,
6      ),
7      builder: (context, player) {
8        return Column(
9          crossAxisAlignment: CrossAxisAlignment.start,
10          children: [
11            player,
12            SizedBox(height: 10.0),
13            Container(
14              padding: EdgeInsets.only(left: 10.0, right: 10.0),
15              child: Text(
16                widget.video.title,
17                style: TextStyle(
18                  fontSize: 15.0,
19                ),
20              ),
21            )
22          ],
23        );
24      }
25  );
26}

We have just one more thing left to do! We must add navigation from the Playlistsscreen, in order to navigate to the Videos screen. Open playlists.dart import the videos screen in the top of the file.

1import '../screens/videos.dart';

Update the onTap parameter with a async method. Within the method, use the Navigator to push a new screen, the Videos screen. Don't forget the playlist parameter.

1onTap: () async {
2  Navigator.push(
3    context,
4    MaterialPageRoute(builder: (context) {
5      return Videos(playlist: playlist,);
6    }),
7  );
8},

Run the app and you should be able to tap on a playlist row and navigate to the videos screen. Once there, should you be able to play the visible videos.

YouTube app playlists

You can find the complete app on GitHub

Tomas Sjösten
Project manager with a passion for coding (especially Flutter)! I set aside several hours a week for various coding projects or writing articles about coding.