ReSwift触ってみた
概要
Swiftでreduxを実現するならReSwiftというパッケージが良いらしいです。
サンプルをクローン
下記が公式のカウンターアプリです。 コードを見てみます。 github.com
ディレクトリ構成
下記のような構成です。
- Reducers - Actions - State AppDelegate ViewController
State
状態の構造体を定義する場所
import ReSwift struct AppState: StateType { var counter: Int = 0 }
Actions
Actionを定義する場所。同一ファイルに全部まとめる
import ReSwift // all of the actions that can be applied to the state struct CounterActionIncrease: Action {} struct CounterActionDecrease: Action {}
Reducers
reducerを定義する場所。(ReducerとはActionとStateを受け取って、処理をまとめる場所)
import ReSwift // the reducer is responsible for evolving the application state based // on the actions it receives func counterReducer(action: Action, state: AppState?) -> AppState { // if no state has been provided, create the default state var state = state ?? AppState() switch action { case _ as CounterActionIncrease: state.counter += 1 case _ as CounterActionDecrease: state.counter -= 1 default: break } return state }
AppDelegate
AppDelegateがまだキャッチアップできていないものの、起動時の時だけmainStoreをインスタンス化してGlobalに扱ってるっぽい。。?
import UIKit import ReSwift // The global application store, which is responsible for managing the appliction state. let mainStore = Store<AppState>( reducer: counterReducer, state: nil ) @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { return true } }
ViewController
ViewとStoreを紐づけたり、UIアクションと処理を紐づけたりしているっぽい
import UIKit import ReSwift class ViewController: UIViewController, StoreSubscriber { typealias StoreSubscriberStateType = AppState @IBOutlet weak var counterLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() // subscribe to state changes mainStore.subscribe(self) } func newState(state: AppState) { // when the state changes, the UI is updated to reflect the current state counterLabel.text = "\(mainStore.state.counter)" } // when either button is tapped, an action is dispatched to the store // in order to update the application state @IBAction func downTouch(_ sender: AnyObject) { mainStore.dispatch(CounterActionDecrease()); } @IBAction func upTouch(_ sender: AnyObject) { mainStore.dispatch(CounterActionIncrease()); } }
まとめ
SwiftUIと組み合わせて使えるものだと期待していたけれど、そういうわけではなさそう。。 Storyboardぽちぽちはあまり得意ではないのでなんとかSwiftUIに慣れていきたい。
スケールするFlutterアプリアーキテクチャで開発させてもらいました🙇♂️
概要
前職のアーキテクチャをざっくり紹介したいな、と思いました。 かなり開発体験良かったもので。。。
誰が作ったか
設計された方は僕が入社した際には既に退職されていました。 ドキュメントが残されていたり、当時のメンバーが残っていたので理解するのは難しくなかったです。
ざっくりUML
こんな感じです。

PODIO
plain old dart immutable objectの略で基本どこからでも呼んでOK(と理解)です。
PODIOに設定するメソッドはデータの提出系のみでロジックはNGでした。
POJOから由来しており、PODIOは「何にも依存しないアプリ内で動くために存在するオブジェクト」と理解していました。
Behavior
役割はトランザクションスクリプトで、後述するStoreに副作用を与えることができる唯一の存在です。 ロジックは基本的に全てBehaviorになります。
あくまで手続きをまとめるという粒度で、「Behaviorがそのままユースケースになる」こともあれば「アプリケーション内で必要な手続きをまとめる」ことも許されていました。つまり、BehaviorがBehaviorを呼び出すのは問題ないというルールでした。
後述するRepositoryを呼び出すのもBehaviorの役割です。
Store
FluxのStoreとほぼ同義です。 アプリの状態を管理するためのオブジェクトです。
アンチパターンとしてはViewと1:1になってしまうことです。
StoreとViewは1:Nとなりviewを更新する対象も自由自在です。
Repository
データ永続化を行います。
基本的にBehaviorからのみ呼び出します。
その他
PODIOは正規化してMap<Id, User>で保持したりするなど他にも細かなルールはあります。
下記がアーキテクチャを発案した方の原文です。
DIしてflutterアプリのテスタビリティを上げる
概要
今更感あるので需要もそんなにないだろうけども、ブログを書くペースを落としたくないので書いてみます。
DI(Dependency Injection)
依存性の注入です。
なんか内部で依存しているやつを外部から注入できるようにするやつです。
依存性の逆転の法則とはまた別なので注意が必要です。
なぜ必要か
- 内部でオブジェクトを生成するとテストしづらいからです。
- 詳細に依存すると結合度が高くなっちゃうから
どうやるか
色々やり方はあります。
- interfaceを定義して引数に渡す
- 必要な関数を引数に渡す
- setterでオブジェクトを注入する
flutterでのケース
よくある(自分が直面した)のはwidgetテストやunitテストの際にモックできないなどでしょう。
例えば、「対象の記事をいいねするボタンをテストする」ケースを想像しましょう。
group('KeepButton', () { testWidgets('will activate [onPressed] call', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: LikeButton( onPressed: (_) { expect('called', 'called'); }, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byType(KeepButton)); });
うまくいきそうでしょうか? 実はテストは落ちてしまいます。
なぜなら内部でLikeButtonがいいねされているかどうかを確認するために、providerを利用していたからです。(Riverpodなら問題ないかもしれないですが、providerだとcontextに載せたインスタンスもmockしなくちゃいけない)
class KeepButton extends StatelessWidget { final void Function(Target target) onPressed; @override Widget build(BuildContext context) { return Selector<AppStore, bool>(selector: (_, store) { // 省略 } }
こういった場合は必要なbool値だけフィールドとして設定し、親Widgetからselectorで取得することで「依存性を注入」することができます。
補足/まとめ
riverpodだとモックとかも簡単にできてなんか便利そうですね。
rubyで公開鍵をダウンロードするscripts
たいまま
なんかrubyで書きたい!ってなったので、特に需要があったわけでもないんですが公開鍵をgithubからダウンロードするscriptを作りました。
require 'logger' require 'net/http' class Task BASE_URL = 'https://github.com' USER_NAME = 'tomoyukitanaka1171' def initialize @uri = URI.parse("#{BASE_URL}/#{USER_NAME}.keys") @logger = Logger.new(STDOUT) @logger.formatter = ::Logger::Formatter.new end def download request = Net::HTTP::Get.new(@uri) request['Accept-Charset'] = 'utf-8' begin res = Net::HTTP.start(@uri.host, @uri.port, use_ssl: true) do |http| http.request(request) end if res.is_a?(Net::HTTPSuccess) filename = 'authorized_key.pub' @logger.info("DOWNLOADED: #{filename}") File.write(filename, res.body) else @logger.error('################') @logger.error(res) @logger.error('################') end rescue => e @logger.error('################') @logger.error(e) @logger.error('################') end end end Task.new.download
rubyのinitializeとか結構好きです。
実際は普通のコンストラクタでinitialize(uri:)とかで名前付き引数も渡せます。
@がついてるのがいわゆるクラスのメンバで、initialize内のローカル変数っぽいですが実際はクラス内であればどこからでもアクセスできます。
↓githubです。
Dartでオブジェクトを等値比較しよう
概要
OOPでプログラミングしていく場合、宣言的にUIを構築できるのがメリットの一つかと思われます。
雑なユースケースですが、Userが「自分自身だった場合」や「管理者アカウトだった場合」に処理や見た目を変えるなどがあると思いますが、その際にUserインスタンスのidを見て比較するわけですが、プリミティブな型でidを保持するのは少々危険かと思います。
dartはインスタンス同士を比較する
By default,
==returns true if two objects are the same instance.
dartではclassのoperatorというメンバをoverrideすることで、独自の比較を設定できます。
ただ、全てのclassをoverrideするのはめんどくさいのでEquatableパッケージを利用するのが主流です。*1
こんな感じでEqutableクラスを継承して、抽象化したクラスを用意します。
abstract class UniqueKey extends Equatable {
@protected
final String id;
const UniqueKey(this.id);
@override
List<Object> get props => [id];
}
利用する側ではこのような感じ。
class TodoId extends UniqueKey {
const TodoId(String id) : super(id);
}
@freezed
class Todo with _$Todo {
const factory Todo({
@Default(TodoId('')) TodoId id,
@Default('') String description,
@Default(false) bool completed,
}) = _Todo;
const Todo._();
}
上記のようにすることで、TodoIdというカスタムクラスが宣言でき、idを安全に利用することが可能になりました。
下記記事をとても参考にさせて頂きました🙏
*1:多分