sharedPreference能將簡單的資料存放在硬碟內,採用key-value的方式做存取。
在Flutter要使用sharedPreference非常簡單,首先先下載「shared_preferences」套件。
在pubspec.yaml dependencies處添加 shared_preferences: ^0.5.3+4安裝最新版本。
要增加的程式碼並不多。
import 'package:shared_preferences/shared_preferences.dart';
  bool _rememberAccount;
_loadCheckbox() async{
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _rememberAccount = prefs.getBool('remember_account') ?? false;
    });
  }
  _saveCheckout(value) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setBool('remember_account', value);
  }
  _loadAccount() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState(() {
      _emailController.text = prefs.getString('account') ?? "";
    });
  }
  _saveAccount(account) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString('account', account);
  }
void initState() {
    _loginBloc = BlocProvider.of<LoginBloc>(context);
    _emailController.addListener(_onEmailChanged);
    _passwordController.addListener(_onPasswordChanged);
    _loadCheckbox();
    _loadAccount();
    super.initState();
  }
CheckboxListTile(
            value: _rememberAccount ?? false,
            onChanged: (value){
                setState(() {
                    _rememberAccount = value;
                });
                _saveCheckout(value);
            },
            title: Text('記住我的帳號'),
            controlAffinity: ListTileControlAffinity.leading,
)
if (state.isSuccess) {
          if(_rememberAccount)
            _saveAccount(_emailController.text);
          BlocProvider.of<AuthenticationBloc>(context).dispatch(LoggedIn());
        }

處理完記錄帳號的功能,回到今天的正題來串接TMDb API吧,TMDb的API是免費的除了提供電影資訊外它也提供名人和TV Show的資料可以使用。


在瀏覽器網址輸入https://api.themoviedb.org/3/movie/now_playing?api_key=[Your API KEY]&page=1®ion=TW,把你的API Key替換進去,應該會看到如下圖的畫面,這些就是TMDb回傳的資料,我們在App裡面就要轉換它們並存成Movie的物件。
如果你覺得這樣看太亂不舒服,可以使用Firefox測試,Firefox偵測到是json格式就會自動的整理排版。
可以到TMDb的API Document查看如何使用他們的API,也能直接在網頁上進行測試,測試好直接複製它們產生的網址之後就可以使用網址取得資料。
這次會使用到以下幾種電影清單類型:
可以先在網頁上玩看看~
新增「movie」資料夾在裡面新增「model」資料夾,最後新增「movie_list.dart」。
路徑:lib/movie/model/movie_list.dart
這裡我們要自己建立MovieList類別,還沒有class概念的人可以參考Day5的教學。
class MovieList {
  int _page;
  int _totalResults;
  int _totalPages;
  List<_Item> _results = [];
  MovieList.fromJson(Map<String, dynamic> parsedJson) {
    _page = parsedJson['page'];
    _totalPages = parsedJson['total_pages'];
    _totalResults = parsedJson['total_results'];
    List<_Item> temp = [];
    for (int i=0; i < parsedJson['results'].length; i++){
      _Item item = _Item(parsedJson['results'][i]);
      temp.add(item);
    }
    _results = temp;
  }
  List<_Item> get movieList => _results;
  int get totalPages => _totalPages;
  int get totalResults => _totalResults;
  int get page => _page;
}
class _Item {
  var _popularity;
  int _voteCount;
  int _id;
  bool _video;
  var _voteAverage;
  String _title;
  String _posterPath;
  String _backdropPath;
  String _language;
  String _overview;
  String _releaseDate;
  _Item(result) {
    _popularity = result['popularity'];
    _voteCount = result['vote_count'];
    _id = result['id'];
    _video = result['video'];
    _voteAverage = result['vote_average'] + 0.0;
    _title = result['title'];
    _posterPath = result['poster_path'];
    _backdropPath = result['backdrop_path'];
    _language = result['original_language'];
    _overview = result['overview'];
    _releaseDate = result['release_date'];
  }
  String get popularity => _popularity;
  int get voteCount => _voteCount;
  int get id => _id;
  bool get video => _video;
  double get voteAverage => _voteAverage;
  String get title => _title;
  String get posterPath => _posterPath;
  String get backdropPath => _backdropPath;
  String get language => _language;
  String get overview => _overview;
  String get releaseDate => _releaseDate;
}
雖然程式碼很長,不過概念很簡單,在這個檔案中我們創造兩個class:
還記得Day5提過的name constructor嗎?MovieList.fromJson(Map<String, dynamic> parsedJson)就是一個name constructor,它能接收從Json data decode來的Map object用來初始化MovieList Object。
在movie資料夾下新增movie_api_provider.dart
由於在這個檔案中我們要使用到http來呼叫IMDb API,所以先到pubspec.yaml增加「http」套件的引用
http: ^0.12.0+2
下載好後來看程式碼吧。
import 'package:http/http.dart';
import 'dart:async';
import 'dart:convert';
import 'model/movie_list.dart';
import 'movie_api_key.dart';
class MovieApiProvider {
  final Client _client;
  String _apiKey = movie_api_key;
  final _baseUrl = 'http://api.themoviedb.org/3/movie';
  MovieApiProvider({Client client})
      : _client = client ?? Client();
  Future<MovieList> fetchPopularMovieList({String region = 'TW'}) async {
    final response = await _client
        .get("$_baseUrl/popular?api_key=$_apiKey&page=1®ion=$region");
    if (response.statusCode == 200) {
      return MovieList.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to get popular movie list.');
    }
  }
  Future<MovieList> fetchNowPlayingMovieList({String region = 'TW'}) async {
    final response = await _client
        .get("$_baseUrl/now_playing?api_key=$_apiKey&page=1®ion=$region");
    if (response.statusCode == 200) {
      return MovieList.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to get now playing movie list.');
    }
  }
  Future<MovieList> fetchTopRatedMovieList({String region = 'TW'}) async {
    final response = await _client
        .get("$_baseUrl/top_rated?api_key=$_apiKey&page=1®ion=$region");
    if (response.statusCode == 200) {
      return MovieList.fromJson(json.decode(response.body));
    } else {
      throw Exception('Failed to get top rated movie list.');
    }
  }
}
你的api_key可以像我一樣放在另一個檔案「movie_api_key.dart」,裡面只有一行程式碼const String movie_api_key = "your_api_key";
建構子需要client參數是為了之後測試方便才加的,目前先不用理會它。
這3個method使用http的Get傳送Request和Server索取前面提到的「Now Playing」、「Popular」和「Top Rated」電影清單。接收到Response後確認statusCode,假如是200代表成功取得資料。
其他statusCode對應的訊息可以在這裡查詢。
在movie資料夾下新增movie_repository.dart
import 'dart:async';
import 'movie_api_provider.dart';
import 'model/movie_list.dart';
class MovieRepository {
  final _movieApiProvider = MovieApiProvider();
  Future<MovieList> fetchPopularMovieList({String region = 'TW'}) =>
      _movieApiProvider.fetchPopularMovieList(region: region);
  Future<MovieList> fetchNowPlayingMovieList({String region = 'TW'}) =>
      _movieApiProvider.fetchNowPlayingMovieList(region: region);
  Future<MovieList> fetchTopRatedMovieList({String region = 'TW'}) =>
      _movieApiProvider.fetchTopRatedMovieList(region: region);
}
其實只是多包裝一層Repository class,這是因為使用MovieRepository的人不需要知道我們是如何呼叫TMDb API的。
之後實作的MovieBloc如果要使用到Movie API就直接呼叫MovieRepository而不是MovieApiProvider。
fluttube
└───lib
│   └───movie
│   │   └───model
│   │   │   └───movie_list.dart
│   │   └───movie_api_provider.dart
│   │   └───movie_repository.dart
│   └───main.dart
│   ...
│   └─── validators.dart
今天前半部幫登入介面新增了記錄帳號的功能,也順便學習如何使用SharedPreference套件(雖然也可以使用SharedPreference來記錄使用者的密碼,不過礙於安全性就不推薦了)。
後半部認識了TMDb API該如何設定和使用。根據回傳的Json Data建立了MovieList Class。
並在App中新增了MovieApiProvider和MovieRepository來串接API,之後就能直接使用MovieRepository內的method取得MovieList。
明天老樣子來實作MovieBloc吧,這已經是實作的第四個Bloc應該很熟悉了迅速完成它吧。
完整程式碼在這裡-> FlutTube Github
您好:
我覺得您的專案看起很棒,想研究一下,
請問目前一個地方在 movie_api_key.dart,
專案說找不到這個dart,請問這來源是什麼呢? 好像也不是外來的
文章中有提到呦
你的api_key可以像我一樣放在另一個檔案「movie_api_key.dart」,裡面只有一行程式碼const String movie_api_key = "your_api_key";
可以嘍!! 感謝!