Kinokのブログ

しゃかいじん。

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は「何にも依存しないアプリ内で動くために存在するオブジェクト」と理解していました。

qiita.com

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>で保持したりするなど他にも細かなルールはあります。

下記がアーキテクチャを発案した方の原文です。

hachibeechan.hateblo.jp

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だとモックとかも簡単にできてなんか便利そうですね。

riverpod.dev

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です。

github.com

Dartでオブジェクトを等値比較しよう

概要

OOPでプログラミングしていく場合、宣言的にUIを構築できるのがメリットの一つかと思われます。

 

雑なユースケースですが、Userインスタンスが自分だった場合に処理や見た目を変えるなど。

 

Userインスタンスのid(それかインスタンス)を見て比較するわけですが、プリミティブな型でidを保持するのは少々危険かと思います。

 

dartインスタンス同士を比較する

 

By default, == returns true if two objects are the same instance.

pub.dev

 

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._();
}

 

なお、こういったFlutterに関するアイデアはhatchinさんという方からインスパイアを受けました。*2

 

hatchinさんはflutterでfluxアーキテクチャを実現していたのですが、これがすごく良い開発体験でした。(一緒に働いていたわけではないです。人生いろいろあるじゃないですか。)

mvvmとかよくわからないのですが、flutterにおいてはfluxが最適解なんじゃ?と思わずにはいられないほど。。

 

許可が取れたら今後も発信していきたいですね。。

すでに記事にされてるので、riverpodでちょっとアレンジしてみるのもいいかもしれないです。

hachibeechan.hateblo.jp

まとめ

この辺のOOPやる上で必要な知見とかもっと深めていきたいですね。(KONAMI

*1:多分

*2:まるパクりです