Flutter CI codecov MIT

GitHub Search

GitHub API を利用して GitHub のリポジトリを検索するアプリです。株式会社ゆめみのFlutterエンジニアコードチェックの要件を満たすよう実装しています。

本アプリを通して自分なりの最適なアーキテクチャを確立し、リファレンスコードにすることを目的にしています。

:warning: 注意
このアプリは GitHub API を利用するために GitHub の アクセストークン をアプリの内部でハードコーディングして保持する構成になっています。このアプリを公開すると悪意のある者に アクセストークン を抜き取られ悪用される恐れがありますのでお控え下さい。もちろん、手元でビルドして動かすことは問題ありません。

github_search_0_5_0_demo

ビルド方法

  • ソースコードを clone してください。
git clone https://github.com/susatthi/github-search.git
  • 本アプリで使用する GitHub の個人アクセストークンを取得してください。

  • カレントディレクトリで次のコマンドを実行してください。

# 引数で与えられた環境変数を基にビルドに必要な `lib/config/env.dart` を作成してくれます。
# 作成された `lib/config/env.dart` を直接編集しても大丈夫です。

bin/flutter_env -g [GitHubアクセストークン] -s [検索文字列の初期値]
パラメータ説明
-gMust値には GitHub 個人アクセストークン を設定してください。
-sOptional好きな文字列を設定してください。指定しない場合は空文字が設定されます。
-hヘルプを表示します。
  • Configurations を選択してビルドしてください
Configurations 名説明
appアプリ(iOS / Android)向け
webWeb 向け

アプリの機能

  • シンプルな UI / UX
    • GitHub リポジトリの検索と詳細表示
    • 検索結果の並び替えと hive を使ったデータの永続化
    • 無限スクロール対応
  • go_router を使った新しいルーティング
  • http を使った REST API の実装
  • fast_i18n を使った多言語対応(日本語/英語)
  • カスタムフォント対応
  • mockito を使った Unit / Widget テスト
  • flutter_launcher_icons を使ったアプリアイコン
  • flutter_native_splash を使ったスプラッシュ画面
  • GitHub Actions による自動テストと自動ビルド
  • サポートするプラットフォーム
    • iOS / Android / Web / macOS / Windows

今後対応予定

  • Integration テスト
  • テーマ対応
  • ダークモード対応
  • よりよい UI / UX

対応しないこと

  • Firebase 連携
  • Flavor 対応(develop / staging / production などの環境分け)

アーキテクチャ / パッケージ

CODE_WITH_ANDREA

  • 本アプリの依存関係図です。
%%{init:{'theme':'base','themeVariables':{'primaryColor':'#f0f0f0','primaryTextColor':'#2f2f2f', 'lineColor':'#2f2f2f','textColor':'#2f2f2f','fontSize':'16px','nodeBorder':'0px'}}}%%
graph TD
    subgraph プレゼンテーション層
    IndexPage(一覧ページ<br>StatelessWidget) --> SearchTextField(検索テキストフィールド<br>ConsumerWidget)
    IndexPage --> ListView(一覧 View<br>ConsumerWidget)
    IndexPage --> OrderToggleButton(オーダー値ボタン<br>ConsumerWidget)
    IndexPage --> SortSelectorDialog(ソート値選択ダイアログ<br>ConsumerWidget)    
    SearchTextField --> SearchText([検索文字列<br>String])
    ListView --> ListViewState([一覧 View 状態<br>State])
    ListViewState --> ListViewController(一覧 View コントローラ<br>StateNotifier)
    ListViewController --> SearchText
    ListViewController --> Order
    ListViewController --> Sort
    OrderToggleButton --> ListViewState
    OrderToggleButton --> Order([オーダー値<br>Enum])
    SortSelectorDialog --> Sort([ソート値<br>Enum])
    Order --> OrderController(オーダー値コントローラ<br>StateNotifier)
    Sort --> SortController(ソート値コントローラ<br>StateNotifier)
    ViewPage(詳細ページ<br>StatelessWidget) --> DetailView(詳細 View<br>ConsumerWidget)
    DetailView --> DetailViewState([詳細 View 状態<br>State])
    DetailViewState --> DetailViewController(詳細 View コントローラ<br>StateNotifier)
    DetailViewController --> ViewParameter([オーナー名とリポジトリ名<br>Equatable])
    end
    subgraph データ層
    ListViewController --> RepoRepository(リポジトリ用リポジトリ)
    DetailViewController ---> RepoRepository
    OrderController --> AppDataRepository(アプリデータ用リポジトリ)
    SortController --> AppDataRepository
    RepoRepository --> GitHubRepoRepository(GitHub 版リポジトリ用リポジトリ)
    AppDataRepository --> HiveAppDataRepository(Hive 版アプリデータ用リポジトリ)
    subgraph DTO
    GitHubRepoRepository --> GitHubHttpClient(GitHub 向け HTTP クライアント)
    GitHubRepoRepository --> GitHubApiDef(GitHub API 定義)
    HiveAppDataRepository --> HiveBox(Hive.box)
    end
    subgraph データソース
    GitHubHttpClient --> GitHubApi(GitHub API)
    HiveBox --> File(File)
    end
    end
    subgraph 環境変数
    SearchText --> EnvSearchText{{検索文字列初期値<br>String}}
    GitHubHttpClient ---> EnvAccessToken{{アクセストークン<br>String}}
    end

    classDef widget fill:#4063DD, color:#ffffff;
    classDef controller fill:#4063DD, color:#ffffff;
    classDef state fill:#BDB5F4, color:#ffffff;    
    classDef repository fill:#437C40, color:#ffffff;
    classDef env fill:#7c7d7c, color:#ffffff;
    class IndexPage,ViewPage,ListView,SearchTextField,DetailView,OrderToggleButton,SortSelectorDialog widget;
    class ListViewController,DetailViewController,OrderController,SortController controller;
    class SearchText,ListViewState,DetailViewState,ViewParameter,Order,Sort state;
    class RepoRepository,GitHubRepoRepository,GitHubHttpClient,GitHubApiDef,GitHubApi,AppDataRepository,HiveAppDataRepository,HiveBox,File repository;
    class EnvSearchText,EnvAccessToken env;
  • 検索実行時に 一覧 View が更新される例
    • 一覧 Viewの依存関係は、一覧 View一覧 View 状態一覧 View コントローラ検索文字列となっています。ユーザが検索文字列を変更し検索を実行した場合、検索文字列が更新されます。すると検索文字列に依存している一覧 View コントローラが更新され、リポジトリ用リポジトリ検索文字列を与えてリポジトリの検索を実行し、その結果をもとに一覧 View 状態を更新します。すると一覧 View 状態に依存している一覧 Viewがリビルドされて再描画されます。
  • ソート値変更時に 一覧 View が更新される例
    • ユーザがソート値選択ダイアログを表示してソート値を変更した場合、ソート値が更新されます。するとソート値に依存している一覧 View コントローラが更新され、リポジトリ用リポジトリソート値を与えてリポジトリの検索を実行し、その結果をもとに一覧 View 状態を更新します。すると一覧 View 状態に依存している一覧 Viewがリビルドされて再描画されます。
  • 詳細 Viewへの画面遷移の例
    • 一覧 ViewListTileがタップされるとオーナー名とリポジトリ名を表示したい内容に更新して詳細ページに画面遷移します。詳細画面が開くと詳細 Viewがビルドされ、詳細 View コントローラも作成されます。詳細 View コントローラオーナー名とリポジトリ名リポジトリ用リポジトリに与えてリポジトリの取得を実行し、その結果をもとに詳細 View 状態を更新します。すると詳細 View 状態に依存している詳細 Viewがリビルドされて再描画されます。

フォルダ構成

フォルダ名説明
/assetsassetsにアクセスする自動生成されるユーティリティクラス
/configアプリケーション、定義値、環境変数
/entitiesモデル層のファイル
リポジトリの戻り値に使うエンティティ
プレゼンテーション層で使うエンティティ(_data suffix がつく)
/localizations言語ファイル(json ファイル)、自動生成されるクラス
/presentation/pagesプレゼンテーション層のファイル
画面Widget
/presentation/componentsプレゼンテーション層のファイル
部品Widget、Controller、State
/repositoriesデータ層のファイル
リポジトリ、データソース
データソースはサブディレクトリで管理
/utils拡張機能、ロガーなど便利クラス

環境

Version
Xcode13.3
Android StudioBumblebee 2021.1.1 Patch 3
Flutter2.10.5
Swift5.6
Kotlin1.6.10
Chrome100

対象 OSバージョン

OS Version
iOS9.0 ~ 15.4
Android8.0 ~ 13

コードの自動生成

  • localizations/*.json ファイルを変更した場合や freezed を使った dart ファイルを変更した場合は次のコマンドを実行してください。
bin/flutter_gen

単体テスト(UnitTest / WidgetTest)

  • ローカルで単体テストを行うためには事前に次のライブラリをインストールしてください。
# lcov のインストール
brew install lcov

# dart_dot_reporter のインストール
flutter pub global activate dart_dot_reporter
  • 次のコマンドで単体テスト(静的解析 => テスト => カバレッジの結果を表示 )を実行します。
bin/flutter_test

API ドキュメント

  • API ドキュメント で公開しています。

  • 生成する場合は次のコマンドを実行してください。

bin/dartdoc

CI

  • GitHub Actions を利用して CI を構築しています。
    • プルリクエストが作成や更新された時、もしくは main または develop ブランチに push されたときに CI が発火します。
%%{init:{'theme':'base','themeVariables':{'primaryColor':'#f0f0f0','primaryTextColor':'#2f2f2f', 'lineColor':'#2f2f2f','textColor':'#2f2f2f','fontSize':'16px','nodeBorder':'0px'}}}%%
flowchart LR
    Start((開始)) --> Analyze(静的解析)
    subgraph テスト
    Analyze --> Test(単体テスト)
    Test --> UploadCoverage(Codecovに結果を送信)
    end
    subgraph ビルド
    UploadCoverage --> BuildAndroid(Androidビルド)
    UploadCoverage --> BuildiOS(iOSビルド)
    UploadCoverage --> BuildWeb(Webビルド)
    UploadCoverage --> BuildMacOS(macOSビルド)
    UploadCoverage --> BuildWindows(Windowsビルド)
    UploadCoverage --> CreateApiDoc(APIドキュメント作成)
    CreateApiDoc --> DeployGitHubPages(GitHubPagesにデプロイ)
    end
    subgraph レポート
    BuildAndroid ---> NotifySlack(Slackに結果を送信)
    BuildiOS ---> NotifySlack
    BuildWeb ---> NotifySlack
    BuildMacOS ---> NotifySlack
    BuildWindows ---> NotifySlack
    DeployGitHubPages --> NotifySlack
    end
    NotifySlack --> End((終了))

    classDef anchor fill:#4063DD, color:#ffffff;
    classDef testJob fill:#4063DD, color:#ffffff;
    classDef buildJob fill:#d32f2f, color:#ffffff;
    classDef reportJob fill:#437C40, color:#ffffff;
    %% class Start,End anchor;
    class Analyze,Test,UploadCoverage testJob;
    class BuildAndroid,BuildiOS,BuildWeb,BuildMacOS,BuildWindows,CreateApiDoc,DeployGitHubPages buildJob;
    class NotifySlack reportJob;

ライセンス

MIT

Libraries

api
app_data_repository
app_data_repository
async_value_handler
cached_circle_avatar
constants
env.default
error_page
exception
extensions
github_search_app
http_client
logger
owner
owner_data
repo
repo_data
repo_detail_view
repo_detail_view_controller
repo_index_page
repo_list_view
repo_list_view_controller
repo_list_view_state
repo_order_toggle_button
repo_repository
repo_repository
repo_search_repos_order
repo_search_repos_query
repo_search_repos_sort
repo_search_text_field
repo_sort_selector_dialog
repo_view_page
search_repos_result
strings.g
url_strategy
url_strategy_noop
url_strategy_web