Flutter hands on : Building a movie listing app using Flutter (Part 2) — Movie Detail Page

Anuj Gupta
7 min readSep 15, 2018

In Part 1 of the tutorial we created the home page which lists the Top Rated movies. If you haven’t read part 1 of this tutorial series. Check out the following link

Now in this tutorial we will be covering how to navigate to the movie detail page and then how to create the UI of the page.

So what we want is that when we click on any movie of the list , it takes us to the detail page of that movie. We have already wrapped each MovieCell in a Flat Button and the reason for doing that was to add a gesture on the movie cell. Now we will define an onPressed gesture which will navigate to the movie detail page.

new Expanded(
child: new ListView.builder(
itemCount: movies == null ? 0 : movies.length,
itemBuilder: (context, i) {
return new FlatButton(

child: new MovieCell(movies,i),
padding: const EdgeInsets.all(0.0),
onPressed: (){
Navigator.push(context, new MaterialPageRoute(builder: (context){
return new MovieDetail(movies[i]);
}));
},

color: Colors.white,
);

}),
)

For navigating to another screen we are using the Navigator class and we are pushing the MaterialPageRoute to our MovieDetail. We are also passing the movie that has been pressed so that we can show the details of the movie on the movie details page.

Now we need to make the MovieDetail Widget with a Scaffold that will act as our Movie Detail Screen. So let’s create a new file in our lib folder called movie_detail.dart. And also import this dart file in our movie_list.dart file.

import 'movie_detail.dart';

In our movie_detail.dart file we will start by importing the packages we will be needing to create this layout.

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

We have already used the flutter/material.dart package but we are going to use this dart:ui package to add a blur effect to our movie poster image.

After importing the required packages we are going to create a Stateless Widget called MovieDetail.

class MovieDetail extends StatelessWidget{

final movie;
var image_url = 'https://image.tmdb.org/t/p/w500/';
MovieDetail(this.movie);
Color mainColor = const Color(0xff3C3261);

@override
Widget build(BuildContext context) {

}
}

Since we are passing the movie which has been pressed from the movie list we are creating a constructor which basically updates the local movie object with the movie object passed to the constructor.

MovieDetail(this.movie);

this is equal to

MovieDetail(movie){
this.movie = movie;
}

So now since the setup is ready let’s start building the ui. Let us decipher the layout . We basically have 3 layers in our layout

  1. The Image as a background
  2. The Blur layer which basically makes the background blurred
  3. The content layer where all the info is clearly visible.

Since these layers are stacked over each other we will be using the Stack Widget.

@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
fit: StackFit.expand,
children: []
)
)
}

Now let’s add the bottom most layer in our stack which is the movie poster as a background image. We have the poster image path from our api. To load image from a url we will use the Image class network constructor.

children: [
new Image.network(image_url+movie['poster_path'],fit: BoxFit.cover),
]

Next we need to add the Blur effect along with a black overlay over our background image. Here we will be making use of the ImageFilter class provided by the dart:ui package.

children: [
new Image.network(image_url+movie['poster_path'],fit: BoxFit.cover,),
new BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
child: new Container(
color: Colors.black.withOpacity(0.5),
),),
]

If we run the app we should have the following output

Next step is to add our content layer. Let’s divide our content layer into multiple rows of UI.

So we have

  1. The Image Poster
  2. Movie title and rating
  3. Movie description
  4. And finally some buttons

So we will be needing a Column to list these elements but we are going to wrap the Column inside a SingleChildScrollView Widget since our movie description may be long and the buttons may go below the screen. So the user should have and option to scroll through the movie description.

children: [
new Image.network(image_url+movie['poster_path'],fit: BoxFit.cover,),
new BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
child: new Container(
color: Colors.black.withOpacity(0.5),
)
,),
new SingleChildScrollView(
child: new Container(
margin: const EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Container(
alignment: Alignment.center,
child: new Container(width: 400.0,height: 400.0,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
image: new DecorationImage(image: new NetworkImage(image_url + movie['poster_path']),fit: BoxFit.cover),
boxShadow: [
new BoxShadow(
color: Colors.black,
blurRadius: 20.0,
offset: new Offset(0.0, 10.0)
)
]
),
),
]
),
),
),
]

Here we are using BoxDecoration to make our movie poster image curve on the corners and adding a blur around the edges to give a feel of elevation.

child: new Column(
children: <Widget>[
new Container(
alignment: Alignment.center,
child: new Container(width: 400.0,height: 400.0,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
image: new DecorationImage(image: new NetworkImage(image_url + movie['poster_path']),fit: BoxFit.cover),
boxShadow: [
new BoxShadow(
color: Colors.black,
blurRadius: 20.0,
offset: new Offset(0.0, 10.0)
)
]
),
),
new Container(
margin: const EdgeInsets.symmetric(vertical: 20.0,horizontal: 0.0),
child: new Row(

children: <Widget>[
new Expanded(child: new Text(movie['title'],style: new TextStyle(color: Colors.white,fontSize: 30.0,fontFamily: 'Arvo'),)),
new Text('${movie['vote_average']}/10',style: new TextStyle(color: Colors.white,fontSize: 20.0,fontFamily: 'Arvo'),)
],
),
),
]
)

To show the movie title we are using a Text widget wrapped in an Expanded widget and another Text Widget to show movie rating. Because of the Expanded widget the rating Text widget automatically becomes right aligned.


new Container(
margin: const EdgeInsets.symmetric(vertical: 20.0,horizontal: 0.0),
child: new Row(

children: <Widget>[
new Expanded(child: new Text(movie['title'],style: new TextStyle(color: Colors.white,fontSize: 30.0,fontFamily: 'Arvo'),)),
new Text('${movie['vote_average']}/10',style: new TextStyle(color: Colors.white,fontSize: 20.0,fontFamily: 'Arvo'),)
],
),
),
new Text(movie['overview'],style: new TextStyle(color: Colors.white,fontFamily: 'Arvo')),
new Padding(padding: const EdgeInsets.all(10.0)),

We are using another Text Widget as a child to show the movie description. Below the Text Widget we are adding a Padding widget to give some spacing between the movie description and the buttons row.

new Text(movie['overview'],style: new TextStyle(color: Colors.white, fontFamily: 'Arvo')),
new Padding(padding: const EdgeInsets.all(10.0)),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
width: 150.0,
height: 60.0,
alignment: Alignment.center,
child: new Text(
'Rate Movie',
style: new TextStyle(
color: Colors.white,
fontFamily: 'Arvo',
fontSize: 20.0),
),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)),
)),
new Padding(
padding: const EdgeInsets.all(16.0),
child: new Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: new Icon(
Icons.share,
color: Colors.white,
),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)),
),
),
new Padding(
padding: const EdgeInsets.all(8.0),
child: new Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: new Icon(
Icons.bookmark,
color: Colors.white,
),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)),
)
),
],
)

The Rate Movie Button is basically a container wrapped by an Expanded Widget to give it the maximum width and uses Box Decoration for making corners curved and giving a purple background colors.

The other two buttons are similarly decorated without being wrapped by the Expanded widget.

Bonus Tip: If you want to add an event on the buttons and do something on clicking the buttons . You can wrap the container inside an Inkwell Widget. For eg.

new Padding(
padding: const EdgeInsets.all(8.0),
child: new InkWell(
onTap: null,
child: new Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: new Icon(Icons.bookmark,color: Colors.white,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)
),
)
),
),

That completes our layout. Your code should look like this and we get the following output.

import 'package:flutter/material.dart';
import 'dart:ui' as ui;


class MovieDetail extends StatelessWidget{

final movie;
var image_url = 'https://image.tmdb.org/t/p/w500/';
MovieDetail(this.movie);
Color mainColor = const Color(0xff3C3261);

@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
fit: StackFit.expand,
children: [
new Image.network(image_url+movie['poster_path'],fit: BoxFit.cover,),
new BackdropFilter(
filter: new ui.ImageFilter.blur(sigmaX: 5.0, sigmaY: 5.0),
child: new Container(
color: Colors.black.withOpacity(0.5),
)
,),
new SingleChildScrollView(
child: new Container(
margin: const EdgeInsets.all(20.0),
child: new Column(
children: <Widget>[
new Container(
alignment: Alignment.center,
child: new Container(width: 400.0,height: 400.0,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
image: new DecorationImage(image: new NetworkImage(image_url + movie['poster_path']),fit: BoxFit.cover),
boxShadow: [
new BoxShadow(
color: Colors.black,
blurRadius: 20.0,
offset: new Offset(0.0, 10.0)
)
]
),
),
new Container(
margin: const EdgeInsets.symmetric(vertical: 20.0,horizontal: 0.0),
child: new Row(

children: <Widget>[
new Expanded(child: new Text(movie['title'],style: new TextStyle(color: Colors.white,fontSize: 30.0,fontFamily: 'Arvo'),)),
new Text('${movie['vote_average']}/10',style: new TextStyle(color: Colors.white,fontSize: 20.0,fontFamily: 'Arvo'),)
],
),
),
new Text(movie['overview'],style: new TextStyle(color: Colors.white,fontFamily: 'Arvo')),
new Padding(padding: const EdgeInsets.all(10.0)),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
width: 150.0,
height: 60.0,
alignment: Alignment.center,
child: new Text('Rate Movie',style: new TextStyle(color: Colors.white,fontFamily: 'Arvo',fontSize: 20.0),),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)
),
)
),
new Padding(
padding: const EdgeInsets.all(16.0),
child: new Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: new Icon(Icons.share,color: Colors.white,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)
),
),
),
new Padding(
padding: const EdgeInsets.all(8.0),
child: new Container(
padding: const EdgeInsets.all(16.0),
alignment: Alignment.center,
child: new Icon(Icons.bookmark,color: Colors.white,),
decoration: new BoxDecoration(
borderRadius: new BorderRadius.circular(10.0),
color: const Color(0xaa3C3261)
),
)

),

],
)
],
),
),
)
]
),
);
}
}

The source code of the complete app is available on github.

Thanks for reading this article. Be sure to clap/recommend as much as you can and also share with your friends. It means a lot to me.

Also, Let’s become friends on Twitter, Linkedin and Github.

--

--