0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

FlutterAdvent Calendar 2022

Day 14

[Flutter] APIの使い方に慣れよう(ゆめみの課題編)

Last updated at Posted at 2022-12-13

yumemi_logo_thum.png

こんにちわ いせきです。 今日も元気にコードを書いています。

今回は、APIを使ってアプリを使ってみたいけど「複雑そうだな。。。」や「難しくて自分にはできないよ」と思う駆け出しエンジニアがいるのではないでしょうか。僕もそうでした!!

上記の方や復習した方に簡単にできると思えるような方法を共有したいと思います。

はじめに

今回使用するAPIです。こちらは、ゆめみの研修課題で使用するAPIとなっています。
この課題の中のissuesの中にある「Session2」をやってみようと思います。

やること

・Reload ボタンをタップして天気予報を取得する
・天気予報を画面に表示する

導入

README.mdに書いてある通りに導入していこうと思います。

1、pubspec.yamlに記載する。

Flutterファイル構造を見てみる
 - .dart.tool
 - .idea
 - android
 - build
 - ios
 - lib
 - test
 - gitignore
 - .metadata
 - analysis_option.yaml
 - pubspec.lock
 - pubspec.yaml //このファイルです〜
 - README.md
 ...(ここまでとしておきます。)

pubspec.yamlのファイルを開いて、整理しつつ、yumemi_weatherを導入してみましょう。

pubspec.yamlの内容をコピペする際にはインデントに注意してください
場合によっては、Pub getできない場合があります。

name: sample_app  #自分が作成したアプリの名前
description: A new Flutter project.

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=2.18.4 <3.0.0'

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  
  #この位置に追加する。
  yumemi_weather:
    git:
      url: https://github.com/yumemi-inc/flutter-training.git
      ref: main
      path: packages/yumemi_weather

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

dependenciesdev_dependenciesのどっちを入れたらいいのかなと思う方もいると思うのでわかりやすく表にします。この詳しい説明や使用例などは、別の記事で書こうと思います。

dependencies dev_dependencies
自分の作っているプロジェクト(パッケージ)を使う人にとっても必要となるパッケージを設定する場合 自分のプロジェクト(パッケージ)の開発段階でのみ必要となる場合 

どうでしょうか?綺麗になったと思います。そして、yumemi_weatherが追加されましたね。
これで、Pub getを押してみてください。

これでAPIのPackageを使えるようになりました。やっていきましょう!!!

yumemi_weatherでできること

では、実際にyumemi_weatherは何ができるかを確認します。

ファイルを確認する
import 'dart:convert';
import 'dart:io';
import 'dart:math';

class Request {
  Request({
    required this.area,
    required this.date,
  });

  final String area;
  final DateTime date;

  factory Request.fromJson(Map<String, dynamic> json) {
    return Request(
      area: json['area'] as String,
      date: DateTime.parse(json['date'] as String),
    );
  }
}

class Response {
  Response({
    required this.weatherCondition,
    required this.maxTemperature,
    required this.minTemperature,
    required this.date,
  });

  final WeatherCondition weatherCondition;
  final int maxTemperature;
  final int minTemperature;
  final DateTime date;

  Map<String, dynamic> toJson() {
    final dateText =
        '${date.year.padLeft(4)}-${date.month.padLeft(2)}-${date.day.padLeft(2)}';
    final timeText =
        '${date.hour.padLeft(2)}:${date.minute.padLeft(2)}:${date.second.padLeft(2)}';

    final timeZoneOffset = date.timeZoneOffset;
    final timeZoneSign = timeZoneOffset.isNegative ? '-' : '+';
    final timeZoneText = '$timeZoneSign${timeZoneOffset.inHours.padLeft(2)}:00';
    return {
      'weather_condition': weatherCondition.name,
      'max_temperature': maxTemperature,
      'min_temperature': minTemperature,
      'date': '${dateText}T$timeText$timeZoneText',
    };
  }
}

enum WeatherCondition {
  sunny,
  cloudy,
  rainy,
}

enum YumemiWeatherError {
  invalidParameter,
  unknown,
}

extension RandomExt on Random {
  int range(int min, int max) => min + nextInt(1 + max - min);
}

extension IntExt on int {
  String padLeft(int width, [String padding = '0']) {
    return toString().padLeft(width, padding);
  }
}

const apiDuration = Duration(seconds: 2);

class YumemiWeather {
  Response _makeRandomResponse({
    WeatherCondition? weatherCondition,
    int? maxTemperature,
    int? minTemperature,
    DateTime? date,
  }) {
    final seed = Random().range(0, 100);
    final random = Random(seed);
    final randomWeather = weatherCondition ??
        WeatherCondition.values[random.nextInt(WeatherCondition.values.length)];
    final randomMaxTemperature = maxTemperature ?? random.range(10, 40);
    final randomMinTemperature =
        minTemperature ?? random.range(-40, randomMaxTemperature);
    final randomDate = date ?? DateTime.now();
    return Response(
      weatherCondition: randomWeather,
      maxTemperature: randomMaxTemperature,
      minTemperature: randomMinTemperature,
      date: randomDate.toLocal(),
    );
  }

  /// Pseudo Weather Forecast API simple ver
  ///
  /// Randomly returns `sunny`, `cloudy`, or `rainy`. For example:
  ///
  /// ```dart
  /// final weatherCondition = YumemiWeather().fetchSimpleWeather();
  /// print(weatherCondition); // "sunny"
  /// ```
  String fetchSimpleWeather() {
    return _makeRandomResponse().weatherCondition.name;
  }

  /// Pseudo Weather Forecast API throws ver
  ///
  /// Set [area] to the area for which you want to get a weather forecast.
  /// Randomly throws `YumemiWeatherError.unknown` or returns `sunny`, `cloudy`,
  /// or `rainy`. For example:
  ///
  /// ```dart
  /// final weatherCondition = YumemiWeather().fetchThrowsWeather('tokyo');
  /// print(weatherCondition); // "sunny"
  /// ```
  String fetchThrowsWeather(String area) {
    final randomInt = Random().range(0, 4);
    if (randomInt == 4) {
      throw YumemiWeatherError.unknown;
    }
    return _makeRandomResponse().weatherCondition.name;
  }

  /// Pseudo Weather Forecast API JSON ver
  ///
  /// Set [jsonString] to the JSON string containing the area and datetime for
  /// which you want to get a weather forecast.
  /// If the parsing fails, throw `YumemiWeatherError.invalidParameter`.
  /// Randomly throws `YumemiWeatherError.unknown` or returns the JSON string
  /// with weather forecast information. For example:
  ///
  /// ```dart
  /// const jsonString = '''
  /// {
  ///     "area": "tokyo",
  ///     "date": "2020-04-01T12:00:00+09:00"
  /// }''';
  /// final weatherJson = YumemiWeather().fetchWeather(jsonString);
  /// print(weatherJson); // "{"weather_condition":"cloudy","max_temperature":25,"min_temperature":7,"date":"2020-04-01T12:00:00+09:00"}"
  /// ```
  String fetchWeather(String jsonString) {
    final Request request;
    try {
      final json = jsonDecode(jsonString);
      request = Request.fromJson(json);
    } catch (_) {
      throw YumemiWeatherError.invalidParameter;
    }

    final randomInt = Random().range(0, 4);
    if (randomInt == 4) {
      throw YumemiWeatherError.unknown;
    }
    final response = _makeRandomResponse(date: request.date);
    return jsonEncode(response);
  }

  String syncFetchWeather(String jsonString) {
    sleep(apiDuration);
    return fetchWeather(jsonString);
  }
}

確認できましたか?最初は??となるかもしれません。

一旦整理しましょう。

・使うことができるメソッド
・メソッドを使うと何が取得できるのか
・何が必要なのか

これらを探してみると理解が深まると思います。

僕なりにメソッドを探してみると4つ見つかりました。
fetchSimpleWeather()
fetchThrowsWeather(String area)
fetchWeather(String jsonString)
syncFetchWeather(String jsonString)

Issuesには「使用する APIは、fetchSimpleWeather()と指定されているので、fetchSimpleWeather()を使っていこうと思います。
初めにやるにはとてもいいちょうど良い内容ですね。

fetchSimpleWeather()をより詳しく捉える

  /// Randomly returns `sunny`, `cloudy`, or `rainy`.
  String fetchSimpleWeather() {
    return _makeRandomResponse().weatherCondition.name;
  }

return以降の _makeRandomResponse()weatherCondition.nameに分けてみてみましょう。

Response _makeRandomResponse({
    WeatherCondition? weatherCondition,
    int? maxTemperature,
    int? minTemperature,
    DateTime? date,
  }) {
    final seed = Random().range(0, 100);
    final random = Random(seed);
    final randomWeather = weatherCondition ??
        WeatherCondition.values[random.nextInt(WeatherCondition.values.length)];
    final randomMaxTemperature = maxTemperature ?? random.range(10, 40);
    final randomMinTemperature =
        minTemperature ?? random.range(-40, randomMaxTemperature);
    final randomDate = date ?? DateTime.now();
    return Response(
      weatherCondition: randomWeather,
      maxTemperature: randomMaxTemperature,
      minTemperature: randomMinTemperature,
      date: randomDate.toLocal(),
    );
  }

enum WeatherCondition {
  sunny,
  cloudy,
  rainy,
}

コードと書かれている説明文をみると
WeatherConditionの中身をランダムに取得ができる。ということです。

では実際に使ってみましょう。

使用例

新しく作成したファイルを用意します。(コメントアウトを削除したもの)

main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            //TODO ここに天気の種類を載せたい。
            Text(
              '天気の情報',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

fetchSimpleWeather()は、3種類の天気をランダムに取得するので、_counterの下にTextを追加してみようと思います。

この使い方をREADME.mdみてみましょう

import 'package:yumemi_weather/yumemi_weather.dart';

void main() {
  final yumemiWeather = YumemiWeather();
  final weatherCondition = yumemiWeather.fetchSimpleWeather();
  print('Weather Condition: $weatherCondition'); // "sunny"
}

これを作成したファイルに使ってみましょう。

main()の中に書かれていますが慌てないで落ち着いて入れてみましょう。最初はどこに入れていいかわからないと思うので、下を参考に入れてみてください。

import 'package:flutter/material.dart';
import 'package:yumemi_weather/yumemi_weather.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  final yumemiWeather = YumemiWeather();
  String _weather = 'アプリを開いた時に入れる値';

  //今回は、カウントアップするメソッドの中に入れてみました。
  void _incrementCounter() {
    setState(() {
      //TODO +ボタンを押すと天気がランダムに取得できる
      _weather = yumemiWeather.fetchSimpleWeather();
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            Text(
              //TODO '天気の情報', →  _weather,に変更
              _weather,
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}


では、実際にビルドしてみてみましょう。

どうでしょうか?取得できましたね!!!意外とシンプルに実装できますね。作成者の方がとても丁寧に作ってくれたので簡単に実装ができました。

別の記事では、他の3つを取り上げてみようと思います。

最後に

とてもいい学習になるリポジトリだと思います。UIの学習だけでなく、APIの使い方、状態管理、テストまで幅広い範囲の学習に取り組めます。Flutterマスターの方が確認のために学習したり、Flutterに少し慣れた方がチャレンジに取り組んでみたりするといいと思います。
少し難易度は高めっぽいです。

参考文献

Githubのリポジトリ

yumemi_weatherのREADME.md

yumemi_weatherの使用例

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?