🥦

for, for in, while, map 繰り返し処理

2024/06/06に公開

対象者

  • Dartでループ処理を書いてる人
  • 普段は、mapしか書けなくて困ってる人💦
  • forで書くにはどうするのか?

Dartのロジックを書くときに、 mapばかり使っております。しかしリファレンスのコードを読むと、for, for in, whileが出てくる😅
mapしか書けないどうしょう〜😭

筆者はロジックに悩むことがあります。最近はアルゴリズムの問題を解いたりして、map以外で書けないのかと探求してます。

APIからデータを取ってくるこのコードを書き換えて使っていこうと思います。おそらく中級者向けの記事かなと思うので、初心者はやめておいた方がいいかも(−_−;)

riverpod + freezedが必要なので追加しておいてください。

APIはこちら使います。
https://jsonplaceholder.typicode.com/posts

freezedでモデルクラスを作成。

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'posts.freezed.dart';
part 'posts.g.dart';


class Posts with _$Posts {
  const factory Posts({
    required int id,
    required String title,
  }) = _Posts;

  factory Posts.fromJson(Map<String, Object?> json)
      => _$PostsFromJson(json);
}

map は、Listの要素を 1 つずつ取り出して、処理を実行する。forEach との違いは、map は、Listの要素を 1 つずつ取り出して、処理を実行するだけでなく、処理の結果をListにして返す。

void main() {
  var names = ['山田', '佐藤', '鈴木'];
  var upperNames = names.map((name) {
    return name;
  });
  print(upperNames);// (山田, 佐藤, 鈴木)
}

httpを使用して、 APIと通信するロジックを作ります。こちらのコードを後で書き換えていきます。Listに取得したデータを格納して、Mapになってるのでこちらを.toListで、Listに変換してあげましょう。

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:provider_lesson/model/posts.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.g.dart';

(keepAlive: true)
Client client(ClientRef ref) {
  return Client(http.Client());
}

class Client {
  final http.Client client;

  Client(this.client);

  Future<List<Posts>> fetchPost() async {
    try {
    List<Posts> posts = [];
    const url = 'https://jsonplaceholder.typicode.com/posts';
    final response = await client.get(Uri.parse(url));
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      // map
      posts = data.map((json) => Posts.fromJson(json)).toList();
      return posts;
    } else {
      return [];
    }
    } catch (e) {
      throw Exception('Failed to load post');
    }
  }
}

View側に表示するには、AsyncNotifierを使います。buildメソッドの中に、初期化された時の処理を書いて、ページが呼ばれたらデータを表示できるようにしましょう。

import 'package:provider_lesson/api/api.dart';
import 'package:provider_lesson/model/posts.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'async_post.g.dart';


class AsyncPost extends _$AsyncPost {
  
  FutureOr<List<Posts>> build() {
    return fetchPost();
  }

  Future<List<Posts>> fetchPost() async {
    final fetch = await ref.read(clientProvider).fetchPost();
    return fetch;
  }
}

View側のコードは今回は、main.dartに全て書きました。

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:provider_lesson/provider/async_post.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyWidget(),
    );
  }
}

class MyWidget extends ConsumerWidget {
  const MyWidget({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final post = ref.watch(asyncPostProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter Demo While'),
      ),
      body: switch(post) {
        AsyncData(:final value) => ListView.builder(
          itemCount: value.length,
          itemBuilder: (context, index) {
            return ListTile(
              title: Text(value[index].title),
            );
          },
        ),
        AsyncError(:final error) => Center(child: Text('Error: $error')),
        _=> const Center(child: CircularProgressIndicator()),
      }
    );
  }
}

これを書き換えていきます。

プロジェクトの説明

繰り返し処理の基本のforを使ってみよう。他の言語でもよくある書き方の回数を指定しただけループ処理をしてみる。

for文

void main() {
  // 0から9までの数字を出力する
  for (var i = 0; i < 10; i++) {
    print(i);// 0から9まで出力
  }
}

見本は、10回だけどリストの値を数えて、空っぽのリストに、.addで格納してあげます。

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:provider_lesson/model/posts.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.g.dart';

(keepAlive: true)
Client client(ClientRef ref) {
  return Client(http.Client());
}

class Client {
  final http.Client client;

  Client(this.client);

  Future<List<Posts>> fetchPost() async {
    try {
    List<Posts> posts = [];
    const url = 'https://jsonplaceholder.typicode.com/posts';
    final response = await client.get(Uri.parse(url));
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      for(var i = 0; i < data.length; i++) {
        posts.add(Posts.fromJson(data[i]));
      }
      return posts;
    } else {
      return [];
    }
    } catch (e) {
      throw Exception('Failed to load post');
    }
  }
}

for in

for文には、 for in がある。
for in は、Listの要素を 1 つずつ取り出して、処理を実行する。

void main() {
  var names = ['山田', '佐藤', '鈴木'];
  for (var name in names) {
    print(name);// 山田 佐藤 鈴木
  }
}

APIのデータを扱うときは、i in dataと書いて、.addでリストにデータを格納できます。

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:provider_lesson/model/posts.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.g.dart';

(keepAlive: true)
Client client(ClientRef ref) {
  return Client(http.Client());
}

class Client {
  final http.Client client;

  Client(this.client);

  Future<List<Posts>> fetchPost() async {
    try {
    List<Posts> posts = [];
    const url = 'https://jsonplaceholder.typicode.com/posts';
    final response = await client.get(Uri.parse(url));
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      // for in
      for(var i in data) {
        posts.add(Posts.fromJson(i));
      }
      return posts;
    } else {
      return [];
    }
    } catch (e) {
      throw Exception('Failed to load post');
    }
  }
}

while文

これは珍しい。ゲーム開発ではよく使う文法だそうですが、普段 Flutterでは使うことはないですね。Widgetでつかうとパフォーマンスを落とすらしい。ロジックでは問題ない。

while 文は条件式が true の間、繰り返し処理を実行する。

void main() {
  var i = 0;// 0からスタート
  while (i < 10) {// 10未満の間、繰り返し処理を実行
    print(i);// 0から9まで出力
    i++;// 1ずつ増やす
  }
}

APIで使った例。条件が、isNotEmptyでなければデータを追加する。removeAtは指定したindexの要素を削除するメソッドです。
while文書くときは、条件分岐のa == nullとかをfor文だったら{}の中に書くけど、最初からやってるイメージです。

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:provider_lesson/model/posts.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'api.g.dart';

(keepAlive: true)
Client client(ClientRef ref) {
  return Client(http.Client());
}

class Client {
  final http.Client client;

  Client(this.client);

  Future<List<Posts>> fetchPost() async {
    try {
    List<Posts> posts = [];
    const url = 'https://jsonplaceholder.typicode.com/posts';
    final response = await client.get(Uri.parse(url));
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      // while
      while (data.isNotEmpty) {
        posts.add(Posts.fromJson(data.removeAt(0)));
      }
      return posts;
    } else {
      return [];
    }
    } catch (e) {
      throw Exception('Failed to load post');
    }
  }
}

感想

今回は、map以外で繰り返し処理をやってみました。公式のリファレンスにチュートリアルがあるので、やってみると参考になるかもしれません。普段無意識で使ってると書いてるけど、for最近まで使ってなかった笑

ロジックに悩まない男になりたい😅

https://dart.dev/codelabs/iterables

Discussion