Welcome back, Glad that you are here. We're building a Movie App with the best coding
practices and tools. Till now in UI, we've made HomeScreen, Navigation
Drawer and App Dialog. Now, let's work on Movie Detail Screen. This will be longer video than compared to
others. You'll land on movie detail screen from 3
places. Tap on Movie Card from Home Screen, Search
Screen and Favourite Movies Screen. On Movie Detail Screen, you'll see AppBar
with back icon and favourite icon, which is a toggle icon. We will save movies in the local database
in future videos so right now, I'll only place the icons in without giving action on tap. Then, we will have big poster of the movie,
followed by Title and Description of the movie. To keep this video/article to a specific topic,
I will create separate video for cast and video in movie details. Let's start coding. Whenever we design any screen, we should think
of the data that it will display. As in case of Movie Detail Screen, we will
show details of any selected movie. As you already know from our previous videos,
domain layer consists of entities, abstract repository and usecases. Entity (Request & Response)
From Data Source, we will get the movie detail by hitting an API. We will get so many unwanted fields from the
data source, but entity must not have all those fields. Entity should only have the fields that required
to show in UI or that are part of some further API calls. Let's create request and response entity keeping
the domain only in mind. In the domain/entities folder, create a new
file movie_params.dart: To fetch the movie details, we require a movie
id. This class acts as a data holder for request
parameters that are required to call the movie detail API. In the domain/entities folder, create a new
file movie_detail_entity.dart: If you look at the data on Movie Detail Screen,
you can see we need title, releaseDate, overview, voteAverage, and backdropPath. You will also need id and posterPath at some
point in future, so let's add them too. Let's consider that id will be enough to compare
two movie detail objects if required. When we are ready with the request and response entity, we can create an abstract method in
the abstract repository. In the domain/repository folder, add a new
method: Future<Either<AppError, MovieDetailEntity>>
getMovieDetail(int id); Here, we create a method that takes in an
id and returns us with either app error or a success object of MovieDetailEntity. Now, let's make a use case that invokes this method. If you have seen previous videos, we are using
use cases only in the Blocs. Create a new use case in the domain/usecases
folder: Since we have already created some usecases
before, so copy any one of them and change the name to GetMovieDetail. Important thing here is that you define the
response type and request type as MovieDetailEntity and MovieParams respectively. The call method works on these types, so change
them as well with correct types. Here, you'll make call to getMovieDetail instead
of getTrending. We're done with domain layer that easy and
fast. Let's make the API call now via Data Layer. If you open the data folder, you'll see errors in repository folder. That's because we haven't implemented the
method that we declared in abstract repository class. Before resolving the error, let's first create
the model class. A model class is the response model, that holds the parsed JSON from the API. If you remember, we use https://javiercbk.github.io/json_to_dart/
for creating the model class from JSON. Head on to https://developers.themoviedb.org/3/
and select MOVIES. On the right side, you can see the details. Put the API_KEY and any movie id. Hit the SEND REQUEST button and copy the response. Paste this response in JSON2DART tool. Copy the generated code in a new file movie_detail_model.dart We have to make some modifications because
sometimes based on JSON, the JSON2DART tool is unable to create proper sub classes. Make all the fields as final and make fromJSON
method as factory method now. You can refer the [DataSources] video for
the similar steps. While you're doing these code changes, you
will also extend the model with entity and use the super constructor to fill in values
in the entity instance. Now, you can add the implementation of repository method in movie_repository_impl.dart. Add async keyword in the method. You'll create the getMovieDetail() from the
data source, which we will create in a moment. Next, as we have done for other methods to
maintain a level of abstraction, use the model instead of entity. Now, we are at the final part of making the API call. In the movie_remote_data_source.dart, add
the abstract method and the implementation: Add declaration in the abstract class MovieRemoteDataSource. Implement the method in MovieRemoteDataSourceImpl
and add async. To fetch the movie details, you have to append
movie/$id to the BASE_URL. Use fromJson() of MovieDetailModel to create
model from JSON. Finally, return the MovieDetailModel. In this case, we can create a Bloc even before we have our UI. Because, Bloc only depends on making API calls,
and we have use case already added. Let's create a bloc. Movie Detail Bloc
Create bloc with name movie_detail: Add a new event in the movie_detail_event.dart: Declare MovieDetailLoadEvent class extending
the MovieDetailEvent. Declare a final field movieId and override
the props() method. Now, in movie_detail_state.dart add some states MovieDetailLoading for showing the loader,
that we will cover in future videos. MovieDetailError for handling errors when
returned from API or network. MovieDetailLoaded will be emitted when the
movie detail api has responded with success MovieDetailEntity object. As you will store the movie details in the
entity, declare the final field and add it in props as well. Now, handle the event and dispatch the state
in the movie_detail_bloc.dart: As you need to make API call using use case,
declare the final field of GetMovieDetail. Allow it to be passed it in constructor also. We will declare all dependencies things in
get_it.dart in a while. Handle the event in mapEventToState. When you have event as load event, make the
API call and store the response in Either type object. Use the fold operator to yield MovieDetailError
or MovieDetailLoaded state with movie details object. We have declared a new use case and a bloc, let's put them in get_it.dart: Just like others, below all use cases, declare
one more GetMovieDetail. And below all blocs, declare dependency for
MovieDetailBloc. As I do it, I found that I have declared dependency
for MovieTabbedBloc in wrong way. We have given new instances of each use case
in the MovieTabbedBloc constructor. Instead, we should take from getItInstance
directly. Update that This way, we are not having 2 instances of
GetPopular, GetPlayingNow and GetComingSoon use case. As of now, we have 2 places from where we
will navigate to the details screen. 1.On tap of Carousel Card
2. On tap of Tab Card From both of these places, you will get the
movie id. This id you have to transmit to the details
screen. This should be straight forward as you can
directly pass id in the MovieDetailScreen constructor. At first, this seems easy and quick. But, we are talking about best practices,
scalable and modular approach. Currently, you have only one parameter. What you would do when you have 10 parameters? Obviously, you will not like giving 10 fields
in the constructor. For that, we will create a separate class
that can have any number of arguments in future. We are entering a new journey, so create a
folder in journeys and create MovieDetailArguments class in that folder Declare a field movieId, as that will be required
to make the API call once we land on the screen. So, the main idea is when we tap on the movie
card, we take the id to the detail screen and make an API call. Now, we have the arguments. So, let's make the screen.
In the same folder, create a new file movie_detail_screen.dart: Create a Stateless widget and add the MovieDetailArguments
as final field as the screen depends on this argument class. Make this field as required so that we make
sure that screen doesn't get called without arguments by mistake. As this is a required field, let's also be
assure that this will never be null. You might be thinking what happens when arguments
are not null but he movieId in arguments is null. Well, assert statement can only take constants
and movieId is not a constant. So, we cannot restrict this. But, in this case API will return with error
and we will show user with appropriate error. Before moving to UI, let's link this screen
to the carousel card and tab cards. Open movie_card_widget in home/movie_carousel
folder and add the linking: In the onTap body, add the linking. Use the push method to push the MovieDetailScreen
in the stack. You will also pass the MovieDetailArguments
by using movieId. Do this same thing in home/movie_tabbed for
movie_tab_card_widget.dart. For now we are using the push method of Navigator but as
we grow in future videos with more screens, we will use pushNamed because that is more
maintainable. Now, run the application and see what happens
when we tap on movie cards. We will navigate to a white screen, that's
because we have an empty container in the screen. Main Layout
Finally, we are on UI. In the MovieDetailScreen add the Bloc and
handle the success, error states: First, make the MovieDetailScreen as Stateful
widget, as we will get the instance of MovieDetailBloc instance and dispose it here itself. Declare a private variable for the bloc. Get the instance in initState(). Dispose the bloc in dispose(). Dispatch the MovieLoadEvent in initState()
, because as soon as we land on this screen we will get the movie details from the API
based on movieId from MovieDetailArguments. In this build(), wrap the Container widget
with BlocBuilder. Since we have the BlocBuilder, we must also
provide the Bloc down the tree so that the child widgets can use that bloc. So, wrap BlocBuilder with BlocProvider and
provide the value as _movieDetailBloc. This whole screen should be in a Scaffold. Comeback to BlocBuilder to handle the states. For now, just write if-else statements to
handle loaded and error state. For rest of the states, we show SizedBox.shrink(). Run the app now and tap on any movie card. You'll navigate to movie detail screen with
primary colour as the screen colour. This is because, we have added vulcan as scaffoldBackgroundColor
in the ThemeData. Take the MovieDetailEntity out of the state
to use it in the further UI. Use Column to layout elements in vertical
order. We will put the top part of this screen in
a separate widget - BigPoster. In the same folder, create a new widget file
- big_poster.dart: Create a Stateless widget. This widget cannot run without movie details,
so declare a final field of MovieDetailEntity type. Also, make sure that the movie details is
a required field. Update the build() by adding Image: You'll start with using CachedNetworkImage
with posterPath. As the backdropPath is in horizontal so we
will use posterPath only. We will show name of the movie and release
date at the bottom of this image, so we will require some overlay on top so that irrespective
of any image, the title is visible. So, wrap this image with Container and give
a foregroundDecoration. Use LinearGradient with 2 colours, first one
being a little transparent with 0.3 opacity. We will use primary colour for this. By default, the direction of gradient is from
left centre to right centre. So, give begin and end explicitly as we should
have darker colour at the bottom side of the image. Now, we'll add the title, release date and average vote. Using Stack, we will use ListTile over the
BigPoster. Put the Positioned widget below the BigPoster: We want to use full width, so give left and
right as 0. Also, give bottom 0, because we want to position
every text at the bottom of BigPoster. ListtTile is the best widget that can be used
here as we need title, description and trailing widgets in the same positions. We will show title of the movie first. Give it headline5 widget. In subtitle, we will show releaseDate below
the title. Give it greySubtitle text style, will create
it in just a moment. Now, at the right most of the list tile, we
need to show vote average. Give violetHeadline6 as text style. Open theme_text.dart and add 2 text styles
in the extensions: By copying everything in subtitle1 and changing
just the colour, we will create new text style. Similarly, copy all properties of headline6
and change to colour to violet. You might be wondering, why extensions are
created at first place. Because text theme has text styles based on
font sizes, not based on colours. So, when we have to use same text styles with
different colours, it should be easier and consistent throughout the app. By creating an extension on TextTheme you
can call these text styles by Theme.of(context).textTheme.greySubtitle1. This will help us when we add multiple themes
and dark modes to the application. We need one more extension, this time on number though. The vote average that is returned by the API
is in decimals and we want to convert it to %. Call convertToPercentageString
on voteAverage and create num_extensions.dart in common/extensions folder: This method will multiple by 10 and then remove
any further decimals. After this we will append % sign. In the BigPoster widget, add another element in Stack at the end. Use Positioned with some margin from left
and right. Add MovieDetailAppBar that we will create
now. In the journeys/movie_detail folder, create
a new file movie_detail_app_bar.dart: Use Row to layout elements in horizontal. Use arrow_back_ios as the left most icon. Use favourite_border as the right most icon. To make these icons have unlimited space in
between, use spaceBetween. From top, consider using the statusBarHeight
from ScreenUtil because the row is touching the phone from top Use GestureDetector to navigate back to the
home screen on click of the back arrow: Give onTap and pop out of the screen for the back arrow Now, you can navigate back and forth. Cast List and Videos Screen.