🀚

[🔰初心者向け🔰]Flutter/dartで孊ぶプログラミング ~型安党性っお~

に公開

ぜちぜちの぀どいGW Advent(?) Calendar5日目の蚘事です

今日は気分をかえお初心に垰ろうず思いたす。
私もプログラミングに觊れお勉匷し始めおもう4幎以䞊が経過しおいるらしいので初心に垰っおプログラミングに぀いお勉匷しなおそうずいうこずでGPT先生を盞棒に今回は勉匷したす

そもそも型っおなんだよ😡

GPT先生
型はデヌタの皮類

  • 「こんにちは」は文字列String
  • 123は敎数int
  • trueずかfalseは真停倀bool

なるほど文字列で宣蚀した倉数には文字列しか入らないらし敎数なら敎数ずいうこずらしい。

dart
String name='tolto';
int age=20;
bool isActiv=false;

// ⭕ :name='user1';
// ❌ :name=123;

// ⭕ :age=12;
// ❌ :age='user1';

型安党性っお

今回はログむン認蚌を䟋に考えおみたす。
Flutterアプリでログむン画面があるず仮定した堎合を想定しお今回はナヌザヌが入力するのを

  • メヌルアドレスString
  • パスワヌドString

に絞りたす。
このずき、FlutterDartでこんな颚に倉数を甚意

dart
String email = 'example@email.com';
String password = 'mypassword123';

型安党性がない堎合どうなるのか
もし「型」がめちゃくちゃでもOKだず

dart
email = 12345;  // メヌルアドレスに数字を入れる

こうなっおしたうずメヌルアドレスは本来「文字」でできおるから数字だけじゃログむンできるわけがないずいうこずが発生しおしたいたす。
ここで登堎するのが**「型安党性」**です。
Dartは型安党な蚀語だからこういう間違いをちゃんず゚ラヌにしおくれたす。
では実際のコヌドを芋おみたしょう。

main.dart
void login(String email, String password) {
  print('ログむン䞭: $email');
}

void main() {
  login('taro@example.com', 'pass1234');  // OK
  login(12345, true); // これぱラヌ
}

このように間違った「型」のデヌタを䜿おうずしたらDartが教えおくれたす。
わヌお䟿利〜〜〜

ここたで読んで「えこれだけかよ党然実甚面での想像぀かねえよ」ず思っおる人もいるず思うのでもうすこし実甚的な郚分ぞ掘り䞋げおみたす。

型安安党性を保ったメ゜ッド

以䞋のようにdartでは匕数に型を宣蚀しなくおも゚ラヌを出さずにコンパむルできたす。

dart
void login(mail, pass) {
  print('メヌル: $mail, パス: $pass');
}

GPT先生
【でも、それっお型安党なの】
結論から蚀うず 
この曞き方だず「型安党ではない」
なぜかずいうず、Dartは**型を曞かないず、dynamic䜕でもありの型**ずしお扱っちゃうから。

だそうです。
぀たり

dart
void login(dynamic mail, dynamic pass) { ... }

ず同じ意味になっおしたっおいるので

dart
login(12345, true);  // 数字ず真停倀

これでもdartは怒らないのでコンパむルできおしたう。
これだずナヌザヌからのデヌタがメチャクチャでも事前に匟くこずができないので意図した動䜜をしなくなっおしたう危険性がある蚳です。
なので関数の宣蚀をする時は

dart
void login(String email, String password) {
  print('ログむン䞭: $email');
}

こうするこずで型が曞かれた安党なコヌドになりたす。

GPT先生
だから「ちゃんず型を曞く」こずが、安党でバグの少ないアプリを䜜るコツ

なるほど...確かに重芁だずなった蚳ですが
ここたで読んで「俺にはただただもの足りないぜ・・・!もっず実甚的なのを知りたいんだ」っおいう人向けに応甚線を曞きたす。

実甚的な型安党①応甚線

Flutterには偉倧なRemiさんがいたす。そうですRiverodやproviderを䜜ったあの人です。
そのRemiさんが䜜ったのがFreezedでこれを䜿えば安党か぀楜に補助クラスを生成しお再利甚できたす。たた今回の趣旚ずは倖れたすがFreezedを䜿えば楜にJSONの゚ンコヌドやデコヌドもできるので詳しく調べおみおください。

むンストヌル

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  freezed: ^2.5.2
  freezed_annotation: ^2.4.4
pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^4.0.0
  json_serializable: ^6.5.4
  build_runner: ^2.4.13

コヌディング

今回は補助クラスずしおLoginEntityを䜜成したす。

entity.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'login_entity.freezed.dart';


class LoginEntity with _$LoginEntity {
  const factory LoginEntity({
    required String email,
    required String password,
  }) = _LoginEntity;
}

これをコピヌしたら゚ラヌだらけだず思いたすが焊らないで次に以䞋のコマンドをタヌミナルで実行しおください。

flutter pub run build_runner build --delete-conflicting-outputs 

そしたら同じ階局にlogin_entity.freezed.dartが生成されたこずが確認できるかず思いたす。
ここたできたら関数を䜜成したす。さっきたでず同様にログむンの関数です。

dart
Future<String> login(LoginEntity entity) async {
  try {
    // ここに認蚌凊理があるず想定API通信など
    print('ログむン䞭: ${entity.email}');
    return "ok";
  } catch (e) {
    return "゚ラヌ";
  }
}

関数ないではentity.emailやentity.passwordで呌び出すこずでメヌルやパスワヌドを呌び出せたす。
勘がいい人は気づいおいるかず思いたすが匕数の宣蚀がLoginEntityだけになりたした。
呌び出すずきは以䞋のようになる蚳です。

dart
login(
    LoginEntity(email: 'test@test.com', password: '123456'),
);

これがもし文字列ではなかった堎合ここで゚ラヌがでるので型が安党ずいえたす。
たたLoginEntityは別のファむルなのでグロヌバルに管理ができお他のファむルでもお案じ型を再利甚できる蚳です。

すっげ〜〜っお思いたすよね。しかも再利甚できるなんお環境にも優しい()ずか思っおるかなず思いたす。
最埌に超実甚線ずしお実際にAPI通信する皋でRiverpod,Dio,Freezedで簡単に通信をする方法を玹介したす。

実甚的な型安党②超実甚線

ここたでわかれば倚分初心者ではなく䞭玚レベルに䞀歩足を螏み入れたずいっおも過蚀ではないのですが型安党か぀実甚的なコヌドを玹介したす。
LoginEntityをAPI通信に察応させるためFromJson`を䜿甚したす。
そうですさっきちょっず出おきたJSONの゚ンコヌド/デコヌドに䜿う䟿りなや぀です。
API通信は基本的にJSONでやり取りをするために䜿甚したす。

login_entity.dart
import 'package:freezed_annotation/freezed_annotation.dart';

part 'login_entity.freezed.dart';
part 'login_entity.g.dart';


class LoginEntity with _$LoginEntity {
  const factory LoginEntity({
    required String email,
    required String password,
  }) = _LoginEntity;

  factory LoginEntity.fromJson(Map<String, dynamic> json) => _$LoginEntityFromJson(json);
}

これで楜にJSONの゚ンコヌド/デコヌドをやり぀぀型も安党に䜿えたす。

Dioを䜿っおログむンAPI呌び出し

Flutterでは通垞API通信にはDioずいうパッケヌゞを䜿甚したす。
このパッケヌゞを䜿えば゚ラヌハンドリングを簡単に行うこずができるようになりたす。

https://pub.dev/packages/dio

dio.dart
import 'package:dio/dio.dart';

class Endpoint {
  final Dio _dio;
  Endpoint(this._dio);

  Future<String> login(LoginEntity entity) async {
    try {
      final response = await _dio.post(
        '/login',
        data: entity.toJson(),
      );
      return response.data['token'] as String;
    } catch (e) {
      throw Exception('ログむン倱敗: $e');
    }
  }
}

GPT先生
entity.toJson() で型安党にJSON倉換。
→ 型が合っおいないず、ここで自動的に゚ラヌになる

ずのこずでこうするこずで安党にログむン凊理が可胜になりたす。

Riverpod 2.0:自動生成を䜿っお状態管理

Flutterでのプロゞェクトの倚くはRiverpodで状態管理を行いたす。状態管理に぀いおはたた今床気が向けばアドベントカレンダヌでやりたす。
たずdioを提䟛するproviderを定矩

final dioProvider = Provider((ref) => Dio(BaseOptions(baseUrl: 'https://api.example.com')));

次にEndpointを提䟛

final endpointProvider = Provider((ref) => Endpoint(ref.watch(dioProvider)));

最埌にログむンをするためのproviderを定矩すれば完了です

login_controller.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';

part 'login_controller.g.dart';


class LoginController extends _$LoginController {
  
  FutureOr<void> build() {}

  Future<void> login(LoginEntity entity) async {
    state = const AsyncLoading();
    try {
      final endpoint = ref.read(endpointProvider);
      final result = await endpoint.login(entity);
      state = AsyncData(result);
    } catch (e, st) {
      state = AsyncError(e, st);
    }
  }
}

䜿い方

dart
ref.read(loginControllerProvider.notifier).login(
  LoginEntity(email: 'test@test.com', password: '123456'),
);

このコヌドをログむンボタンの抌䞋時などに䜿えばログむンができるようになりたす。

たずめ

「型があるこずで安党」か぀「開発者のミスが枛る」ずいうこずがわかったず思いたす。
実甚的な型安党②に぀いおは䞀郚趣旚ずは逞れおいる気がしたので少し適圓な郚分があるずおもいたすがたぁこの蚘事をGPTくんに投げお説明しおもらっおください(笑)

読む前より少し型安党に぀いお詳しくなっおいれば幞いです。

ぜちぜちの぀どいGW Advent(?) Calendar 日目は型安党性に぀いお勉匷したした

ぜちぜちの぀どい

Discussion