It is a great pleasure to develop an App with Flutter. Its excellent performance, versatility, and numerous native components are all reasons why we chose Flutter! Today we are going to use Flutter to develop a movie App. First take a look at the screenshots of Flutter.

From the main dart began

Dart is where the application starts in Flutter:

import 'package:flutter/material.dart';
import 'package:movie/utils/router.dart' as router;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'movie',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      onGenerateRoute: router.generateRoute,
      initialRoute: '/',); }}Copy the code

Generally, there are two ways to manage routes in Flutter. One is to directly use navigator.of (context).push(). This method is suitable for very simple applications. The route definition is managed in a separate file utils/router.dart: utils/router.dart

import 'package:flutter/material.dart';
import 'package:movie/screens/home.dart';
import 'package:movie/screens/detail.dart';
import 'package:movie/screens/videoPlayer.dart';

Route<dynamic> generateRoute(RouteSettings settings) {
  switch (settings.name) {
    case '/':
      return MaterialPageRoute(builder: (context) => Home());
    case 'detail':
      var arguments = settings.arguments;
      return MaterialPageRoute(
          builder: (context) => MovieDetail(id: arguments));
    case 'video':
      var arguments = settings.arguments;
      return MaterialPageRoute(
          builder: (context) => VideoPage(url: arguments));
    default:
      returnMaterialPageRoute(builder: (context) => Home()); }}Copy the code

This is exactly like the route definition in the front end, where you import components and then return them in their respective routes.

Home page

Use TabBar on the front page to show “trending” and “TOP250”:

import 'package:flutter/material.dart';
import 'package:movie/screens/hot.dart';

class Home extends StatefulWidget {
  Home({Key key}) : super(key: key);

  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, initialIndex: 0, length: 2);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TabBar(
          controller: _tabController,
          tabs: <Widget>[
            Tab(text: 'It's on fire.'),
            Tab(text: 'TOP250'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: <Widget>[
          Hot(),
          Hot(history: true),,),); }}Copy the code

The layout of the two pages is the same, only the data is different, so we reuse the page Hot and pass the history parameter to indicate whether the page is Top250

Hot components for reuse

  • In this component, the two pages are distinguished by the history field.
  • On the pageinitStateRequest data and display it accordingly.
  • The drop-down refresh function is to use the RefreshIndicator component in itsonRefreshFor the logical processing of the drop down.
  • Flutter does not directly provide pull-up loading components, but is also easy to implement throughListViewController can make a judgment: whether the current scrolling position has reached the maximum scrolling position_scrollController.position.pixels == _scrollController.position.maxScrollExtent
  • In order to obtain good user experience, the Tab to switch back and forth, we don’t want the page to render, provided with class AutomaticKeepAliveClientMixin Flutter, overloadingwantKeepAliveHere is the complete code:
import 'package:flutter/material.dart';
import 'package:movie/utils/api.dart' as api;
import 'package:movie/widgets/movieItem.dart';

class Hot extends StatefulWidget {
  final bool history;
  Hot({Key key, this.history = false}) : super(key: key);

  _HotState createState() => _HotState();
}

class _HotState extends State<Hot> with AutomaticKeepAliveClientMixin {
  List _movieList = [];
  int start = 0;
  int total = 0;
  ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      if(_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { getMore(); }});this.query(init: true);
  }

  query({bool init = false}) async {
    Map res = await api.getMovieList(
        history: widget.history, start: init ? 0 : this.start);
    var start = res['start'];
    var total = res['total'];
    var subjects = res['subjects'];
    setState(() {
      if (init) {
        this._movieList = subjects;
      } else {
        this._movieList.addAll(subjects);
      }
      this.start = start + 10;
      this.total = total;
    });
  }

  Future<Null> _onRefresh() async {
    await this.query(init: true);
  }

  getMore() {
    if(start < total) { query(); }}@override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return RefreshIndicator(
      onRefresh: _onRefresh,
      child: ListView.builder(
        controller: _scrollController,
        itemCount: this._movieList.length,
        itemBuilder: (BuildContext context, int index) =>
            MovieItem(data: this._movieList[index]), ), ); }}Copy the code

Movie details page

Click on a single movie using navigator.pushnamed (context, ‘detail’, arguments: data[‘id’]); Can jump to the detail page, in the detail page through the ID request interface to obtain details:

import 'package:flutter/material.dart';
import 'package:movie/widgets/detail/detailTop.dart';
import 'package:movie/widgets/detail/rateing.dart';
import 'package:movie/widgets/detail/actors.dart';
import 'package:movie/widgets/detail/photos.dart';
import 'package:movie/widgets/detail/comments.dart';
import 'package:movie/utils/api.dart' as api;

class MovieDetail extends StatefulWidget {
  final id;
  MovieDetail({Key key, this.id}) : super(key: key);

  _MovieDetailState createState() => _MovieDetailState();
}

class _MovieDetailState extends State<MovieDetail> {
  var _data = {};

  @override
  void initState() {
    super.initState();
    this.init();
  }

  init() async {
    var res = await api.getMovieDetail(widget.id);
    setState(() {
      _data = res;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _data.isEmpty
          ? Center(child: CircularProgressIndicator(),)
          : SafeArea(
              child: Container(
                height: MediaQuery.of(context).size.height,
                width: MediaQuery.of(context).size.width,
                child: ListView(
                  scrollDirection: Axis.vertical,
                  children: <Widget>[
                    MovieDetailTop(data: _data),
                    Rate(count: _data['ratings_count'], rating: _data['rating']),
                    Container(padding: EdgeInsets.all(10),child: Text(_data['summary'])),
                    Actors(directors: _data['directors'], casts: _data['casts']),
                    Photos(photos: _data['photos'],),
                    Comments(comments: _data['popular_comments'[() [() [() [() [() [() }}Copy the code

In the details page, we have encapsulated some components to make the project easier to read and maintain. The specific implementation of the components is not described in detail, but some common native components. These components are:

  • widgets/detail/detailTop.dartMovie overview at the top of the page
  • widgets/detail/rateing.dartGrading components
  • widgets/detail/actors.dartThe cast
  • widgets/detail/photos.dartstill
  • widgets/detail/comments.dartComment on the component

Where do the real numbers come from?

The data in the application is pulled from the Douban developer API, which is in in_theaters, top250top250 and subject/ ID. Apikey is required to request these interfaces, so that the data can be easily requested. I uploaded the Apikey to Github. Please be gentle and don’t blow the apikey dry.

A link to the

Source repository blog address nuggets address