読者です 読者をやめる 読者になる 読者になる

そーす

福岡在住のプログラマ

ミラースタンドを自作した

f:id:saburesan:20170110091603j:plain

うちには長らくミラーがなくて、欲しいやつとかは結構高かったので作ることにした。 写真は完成品。

  1. ニトリのミラー
  2. ナフコの木材

ウォールミラー(ソレイユ) | ニトリ公式通販 家具・インテリア・生活雑貨通販のニトリネット

金額

2500円位かな

パームレスト自作2

前回初めて自作して思いの外良かったので職場+家用で2個欲しくなり作りました

材料は前回と同じなのですが、今回は2つあるうちの1つを6mmの厚さの板に変更しました。

f:id:saburesan:20170107100113j:plain

厚さがHHKBの土台の高さピッタリになりました!!!!!!

あー、きもちぃぃーーーーー

ちなみに今回はナイフ形のヤスリを購入して手首が当たる部分の角を結構滑らかにするように加工しました。

接着するときもおもりを乗せて隙間が出来ないようにしたりと、

f:id:saburesan:20170107100836j:plain

前回の反省を活かして制作した甲斐があってか、前回よりも使いやすいものに仕上がりました。

厚さも軽さもこっちのほうが良いのでこれをもう一つ作ろうかな。

(ちなみに前回作ったパームレストはディスプレイスタンドとして活躍してます)

データが無い日をUnionで補完する

MySQL

BIツールなどを使ってグラフとかを日付順に出したい時に

「今日の〇〇のデータは無いけど△△では今日のデータあるので〇〇のグラフも今日の表示が欲しい」

みたいなことがある場合の対処法です。

Unionでデータを追加する

DBにデータを入れるわけには行かないのでクエリの結果にのみ入れるようにします。

例えばpaymentsというテーブルから過去1ヶ月分の日毎の支払件数を出したい場合は、

SELECT date(paid_at) AS date,
       count(amount) AS COUNT
FROM payments
WHERE paid_at IS NOT NULL
  AND date(paid_at) >= date_sub(CURRENT_DATE, interval 1 MONTH)
GROUP BY date
ORDER BY date DESC

こんな感じでとりあえずはとれます。

ただ、今日取引が無かった場合はグラフには表示されません(X軸は昨日までの表記になる)

そこでUNIONを使ってダミーデータを追加します。

SELECT date(t.paid_at) AS date,
       count(t.amount) AS COUNT
FROM (
        (SELECT paid_at,
                amount
         FROM payments
         WHERE paid_at IS NOT NULL)
      UNION
        (SELECT CURRENT_DATE AS paid_at,
                                NULL AS amount)) t
WHERE t.paid_at IS NOT NULL
  AND date(t.paid_at) >= date_sub(CURRENT_DATE, interval 1 MONTH)
GROUP BY date
ORDER BY date DESC

ポイントはUNIONはカラム数カラム名を同じにしなければならないことです。 ダミーデータの方をpaymentsテーブルのカラムに合わせてもよいのですが、 カラムが多いと面倒なので、元のpaymentsテーブルも必要なカラムのみをSELECTするようにしたほうが楽なんじゃないかなと思います。

UNIONじゃなくて他にも方法あるのかなー

不要なCOUNT関数を追加すると何故か高速化するSELECT

MySQL

最近データ分析をやることが多くSQLばっかり書いてます。

といってもSQLとかDBに関する知識はMAX100とすると私は「2」くらいです。

なので、色々と躓きます。

昨日もちょっとわからないことがありました。

例えば、日毎の累計ユーザが欲しい場合のクエリは

SELECT date(created_at) AS created_on,
  (SELECT count(*)
   FROM users
   WHERE date(created_at) <= created_on) AS COUNT
FROM users t
WHERE t.created_at >= date_sub(CURRENT_DATE, interval 7 DAY)
GROUP BY created_on
ORDER BY created_on DESC

こんな感じになるかと思います。

これは最近の1週間分なんですが、コレだとめちゃくちゃ遅いです。

サブクエリでも毎回集計してるから遅いのは仕方ないとは思うのですが、

このSELECTに

COUNT(*) AS today_count,

というのを追加して

SELECT date(created_at) AS created_on,
       COUNT(*) AS today_count,

  (SELECT count(*)
   FROM tokopay_users
   WHERE date(created_at) <= created_on) AS COUNT
FROM tokopay_users t
WHERE t.created_at >= date_sub(CURRENT_DATE, interval 7 DAY)
GROUP BY created_on
ORDER BY created_on DESC

とすると、何故か爆速になりました。

私には全く理由がわかりません。

誰か教えて欲しい。

HHKBのパームレストを100均アイテムで自作した話

HHKBめっちゃ使いやすいんだけど、私は指が短いので一番上の列のキーが結構遠い。

MBPのキーボードの時は特に感じなかったので、おそらくHHKB自体の高さがある分距離が長くなったんだろうと思い

パームレストが欲しくなりました。

売ってるやつは4000円とかなんで、ちょと高いなーと思ってたら

結構自作してるひといるみたいだったので自分も100均を漁りに行って作りました。

はいこれ

f:id:saburesan:20170106081618j:plain

材料

  • 100円木材(40 * 12 * 9) を2つ
  • 100円木工用ボンド
  • 100円紙やすり
  • オイルステイン塗料(家にたまたまあった)
  • つや消しスプレーニス(家にたまたまあった)

ってことで432円ですね。

作り方

  1. ボンドで木材をくっつける
  2. 角を取るようにヤスリで削る
  3. オイルステインを塗る
  4. スプレーニスを吹きかける
  5. 完成!

使用感

結構良い。

土台よりも若干高さが出てしまったなーと思っていたけれど

手をおいたときに自然とパームレストとスペースの高さが同じになったため

親指の位置がちょうどよい。

f:id:saburesan:20170106082938j:plain

横幅が長いので時間あるときに切ろうかな。

Google Dagger2の公式サンプルをKotlinで書き直してみた

これをAndroid Kotlinで書き直しました。

元のサンプル github.com

Android Kotlin Ver

GitHub - ryohlan/Dagger2KotlinSample: Dagger2 sample written by Kotlin

経緯

Daggerに関しては色々記事は読んだもののイマイチわからなくて、かと言って使わないといけない状況も無かったので放置していた。

最近Android界隈も目新しいことがあまりなくなって(来たように感じて)きて、なんか新しいこを学ばないとなーと思っていた。

テスト

Androidではちゃんとテスト書いたこと無いし、テストを意識したような設計もやったことないです。ちょっとして機能のテストなら書いたことあるくらい。

Android Clean Architecture は避けてた勢です。

そろそろちゃんとそのあたり学ばなと行けないと思い、Android Clean ArchitectureとDaggerを採用して個人アプリを作りながら理解を深めようと思っている。

UIScrollViewは基本的にUIStackViewを内包していいのでは

iOS Swift

iOS8を切れる状況なら、ですが。

class StackScrollView: UIScrollView {
    private(set) lazy var stack: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.alignment = .center
        return stack
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(stack)
        stack.snp.makeConstraints { (make) in
            make.top.equalTo(self)
            make.left.equalTo(self)
            make.right.equalTo(self)
            make.centerX.equalTo(self)
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        contentSize = stack.frame.size
    }

 func push(view: UIView) {
        stack.addArrangedSubview(view)
    }
}

レイアウトにはSnapKit使ってます。 github.com

色なめんどくさいことから解放される感。

カラムの情報をモデルに出力するGem「annotate」

Ruby Rails Gem

f:id:saburesan:20161031001347p:plain

最近個人アプリでサーバーサイドも作りたくなったので目下勉強中です。

Railsチュートリアルは一通りやってたのですが、アプリのAPIとなると結構わからない部分が多くて中々進みません。

テーブルのカラムの情報をschema.rb見ながらモデルクラスにバリデーション書いたりするのが結構めんどくさいなーと思っていたら、

カラム情報をモデルクラスに出力してくれるgemがあることを知りました。初学者にはありがたい。

インストール

gem 'annotate'
bundle install

カラム情報を書き込む

カラムに変更を加えたら以下を実行

bundle exec annotate

すると

# == Schema Information
#
# Table name: users
#
#  id               :integer          not null, primary key
#  email            :string(180)
#  crypted_password :string(255)
#  salt             :string(255)
#  created_at       :datetime
#  updated_at       :datetime
#  token            :string(180)
#  hoge             :string(255)
#

class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates_uniqueness_of :email, allow_blank: true
end

Retrofitで503の時にメンテナンスモードを表示する

f:id:saburesan:20160906091652j:plain

Kotlinは特に関係ないけど。

Retrofitって503ってデフォルトでリクエスト成功の処理に回されるんですかね?

私の環境だと503を成功として扱っていて困りました。

また、デフォルトで成功処理の場合はステータスコードが取得出来ないのでRetrofitでAPIのinterfaceの定義を変更する必要がありました。

ただ、もともとSchedulerのの設定などを共通で行うExtensionを作っていたので変更はすぐに出来ました。

以下変更点

・成功時もステータスコードを取得できるように

//変更前
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<SetupResponse>

//変更後 retrofit2のResponseクラスを使う
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<Response<SetupResponse>>

・Schedulerなどの共通処理

//変更前
fun <T> Single<T>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(subscriber)

//変更後
fun <T> Single<Response<T>>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(object : SingleSubscriber<Response<T>>() {
        override fun onSuccess(value: Response<T>?) {
            value?.let {
                if (it.isSuccessful) {
                    subscriber.onSuccess(it.body())
                } else {
                    onError(error = HttpException(it))
                }
            } ?: onError(error = null)
        }

        override fun onError(error: Throwable?) {
            subscriber.onError(error)
        }
    })
}

・エラーメッセージを整形する処理 SingleSubscriberを継承した抽象クラスです。 レスポンスがエラーの場合はエラーメッセージを配列で受け取るのでそれらを表示する形式に整形して返します。 statusCodeが503の場合にメンテナンス用Activityを別タスクで起動して処理を終了させます。

abstract class ApiSubscriber<T> : SingleSubscriber<T>() {
    override fun onError(error: Throwable?) {
        error?.printStackTrace()
        val httpException = error as? HttpException
        val statusCode = httpException?.code() ?: 0
        val errorMessage = when (statusCode) {
            0 -> App.context.getString(R.string.network_error)
            503 -> {
                MaintenanceActivity.start()
                return
            }
            else -> httpException?.response()?.errorBody()?.string()?.let { GsonWrapper.gson.fromJson(it, ErrorMessagesResponse::class.java)?.errors?.reduce { s1, s2 -> "$s1\n$s2" } }
                    ?: App.context.getString(R.string.network_error)
        }
        onError(ErrorResponse(statusCode = statusCode, message = errorMessage))
    }

    abstract fun onError(errorResponse: ErrorResponse)
}

・メンテナンス用Activity

class MaintenanceActivity : AppCompatActivity() {
    private val view by lazy {
        val webView = WebView(this)
        webView.setWebViewClient(WebViewClient())
        webView
    }

    companion object {
        fun start() {
            val intent = Intent(App.context, MaintenanceActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            App.context.startActivity(intent)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(view)
        view.loadUrl(MAINTENANCE_URL)
    }
}

文章中の任意のテキストをクリック可能にする

Swift iOS

f:id:saburesan:20160427075732p:plain

UITextFieldを使う

というかUITextFieldじゃないとクリックイベントが発火しないようです。 UILabelとかUITextViewはだめみたいです。

クリック可能な部分のテキストのスタイル定義

UITextFieldのlinkTextAttributesプロパティにスタイルをセットします。

たとえば、テキストを白にして下線を付けるとすると

textField.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.white NSUnderlineStyleAttributeName: 1]

こうなります。 リンクテキストのスタイルはattributedTextプロパティでは上書きできないのでlinkTextAttributesを必ずセットする必要があります。

NSMutableAttributedStringを作成する

リンクを含む全体のテキストで初期化します。

let attr = NSMutableAttributedString(string: "テキスト、リンク1、リンク2")

リンクさせたいテキストの位置を取得する

let startIndex = attr.string.distance(from: attr.string.startIndex, to: attr.string.range(of: "リンク1")!.lowerBound)

これでstatIndexには文章中のリンク1の先頭位置が入ります。 決め打ちでやるよりも、文章の変更や多言語対応などを考えると計算したほうがよいでしょう。

あとはスタイルをattrに追加するだけです。

attr.addAttribute(NSLinkAttributeName, value: "開くURL", range: NSRange(location: startIndex, length: "リンク1".characters.count))
textField.attributedText = attr

Statusbarのスタイルを変更する

iOS Swift

f:id:saburesan:20160427075732p:plain

LaunchScreenとここのViewControllerごとに変更しなければならないようです。

LaunchScreenの変更

Info.plistにStatus bar styleを追加します。 f:id:saburesan:20160921132857p:plain

ViewControllerごとの変更

UIViewControllerのpreferredStatusBarStyleプロパティをオーバーライドしましょう

 override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }

結果

f:id:saburesan:20160921133144p:plain

基本的にCustomViewを使うメリット(Adapter編)

class ItemAdapter(context: Context) : ArrayAdapter<Item>(context, 0, mutableListOf<Item>()) {
    var delegate: EventDelegate? = null

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val view = (convertView as? ItemView) ?: ItemView(context)
        view.delegate = delegate
        view.set(getItem(position))
        if (position == count - 1) {
            delegate?.onCalledLastItem()
        }
        return view
    }

    interface EventDelegate : ItemView.EventDelegate {
        fun onCalledLastItem()
    }
}

getViewの中すっきりするよね。

基本的にCustomViewを使うメリット(全体設計編)

Android AndroidStudio Kotlin

基本的にCustomViewってどういうことだよ、って話ですが…

例えば, Activityだと

class MainActivity: AppCompatActivity(){
  private view by lazy { MainView(this) }
  
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(view)
  }
}

class MainView: FrameLayout {
   constructor(context: Context?) : super(context)
   constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
   constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

   init {
      LayoutInflater.from(context).inflate(R.layout.main, this)
   }
}

こうしようって話です。

なぜ?

Viewに関するロジックと操作を分離したいからです。 Androidにかぎらずアプリ開発ではModel部分のロジックよりもView側のロジックの方が複雑だったり大量にコード書く必要があったりしますよね。 setContentView(R.layout.main)だと、ActivityにView側のロジックやViewの参照を持たせなければならずActivityが肥大化し、 ActivityはActivityでしか受け取れないイベントなどを処理する必要があるのでActivityの責務が大きくなりすぎます。 なので、CustomViewにしてView側の処理はすべてCustomViewに隠蔽してしまおうという話です。

実装例

わかりやすいかどうかわかりませんが、 例としてユーザ情報(画像、名前、メールアドレス)を表示して名前とメールアドレスを編集してサーバに送る機能を実装するとしましょう。

Activityに直接書く例

class MainActivity : AppCompatActivity() {
    private val iconView by lazy { findViewById(R.id.iconView) as ImageView }
    private val nameForm by lazy { findViewById(R.id.nameForm) as EditText }
    private val emailForm by lazy { findViewById(R.id.emailForm) as EditText }
    private val sendButton by lazy { findViewById(R.id.sendButton) as Button }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val user = User.load()
        Picasso.with(this).load(user.iconUrl).into(iconView)
        nameForm.setText(user.name)
        emailForm.setText(user.email)
        sendButton.setOnClickListener { send() }
    }

    private fun send() {
        val name = nameForm.text.toString()
        val email = emailForm.text.toString()

        //送信処理…
        //成功したら
        //...
        //失敗したら
        //...
    }
}

これをCustomViewで書き直してみます。 ちなみに、KotlinAndroidExtensionを用いています。

//Activity
class MainActivity : AppCompatActivity(), MainView.EventDelegate, MainModel.EventDelegate {
    private val view by lazy { MainView(this) }
    private val model by lazy { MainModel(this) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(view)

        view.set(user = User.load())
    }

    override fun onClickedSendButton(name: String, email: String) {
        model.send(name = name, email = email)
    }

    override fun onSucceededSending() {
    }

    override fun onFailedSending() {
    }
}

//View
class MainView : FrameLayout {
    val delegate: EventDelegate?

    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    init {
        LayoutInflater.from(context).inflate(R.layout.activity_main, this)
        delegate = context as? EventDelegate
        sendButton.setOnClickListener { delegate?.onClickedSendButton(name = nameForm.text.toString(), email = emailForm.text.toString()) }
    }

    fun set(user: User) {
        Picasso.with(context).load(user.iconUrl).into(iconView)
        nameForm.setText(user.name)
        emailForm.setText(user.email)
    }

    interface EventDelegate {
        fun onClickedSendButton(name: String, email: String)
    }
}

//Model
class MainModel(delegate: EventDelegate?) {
    val delegate = delegate

    fun send(name: String, email: String) {
        //Apiとか叩く処理…
        //成功したら
        delegate?.onSucceededSending()
        //失敗したら
        delegate?.onFailedSending()
    }

    interface EventDelegate {
        fun onSucceededSending()
        fun onFailedSending()
    }
}

機能がシンプルすぎるのでコード量はCustomViewの方が増えましたね(笑)

しかし、責務を上手く分割できたかと思います。

設計としてはModelViewPresenterになるのでしょうか。Activity自体がイベントを直接受け取る場合もあるのでCなのかPなのか私はよくわかりません。とりあえずPとします。

Model -> MainModel

View -> MainView

Presenter -> MainActivity

って感じですかね。

Model

Modelは描画に依存しないロジック部分を担当します。大体がサーバやローカルキャッシュとのやり取りですね。 ModelはViewとPresenterのことは全く知りません。 Modelはメソッドを公開していますが、誰が叩いているかは気にしません。メソッドの処理の結果に応じて適切なdelgateインスタントのメソッドを呼ぶだけです。

View

Viewは特定のデータを加工したりして表示したり、Viewで受け取ったイベントを処理します。 ViewもModelとPresenterのことは全く知りません。 CustomViewを使わない方ではActivity自体が何をどう表示するかを知っている必要がありましたが、CustomViewにすることによってActivityはUserクラスのインスタントを渡すことだけを行えばよいので表示に関する責務がなくなりました。 また、ViewはViewで起こるイベントをdelegateインスタントに移譲します。

Presenter

PresenterだけがModelとViewの存在を知っておりViewから流れてくるイベントに対して適切にModelのメソッドを読んだりModelからのコールバックイベントをViewに反映したりします。 上記のView, Modelの設計にすることでActivityはイベントを受け取って適切にModelとViewにイベントを流すだけの存在と成り果てました。

設計に正解はない

です。すべてにおいてこの設計が良いわけではないです。自分も未だに試行錯誤してます。ただ、AndroidCleanArchitectureのように細けるのは個人的にはどうなのかなーと思います。 結構おおざっぱな設計だとは思いますが、ModelとViewが疎結合であることは大きなメリットだと思います。 多人数で実装を並行に行っても問題起きないですし。 あと今回のCustomViewを使った設計の良いところはActivityからViewの個々のオブジェクトを参照する必要がなく、 CustomViewのパブリックなプロパティ、メソッドとEventDelegateを見ればViewの責務が理解できることだと思います。 Activityから直接参照すると何でもできてしまうのでそのViewの責務が曖昧になってしまい、運用面での不安が残ります。

なんか意外とアプリの設計に関する資料って出てこないんですよね。人の設計見たい。 まだ書き足りない部分ありますが、疲れました。

とりあえずKotlinいいよ、Kotlin.

Structural Subtyping(構造的部分型)についての現時点での理解

なんで気になったかというとたまたまフォローしてる人らがタイムラインで言い合いしててそこの出てきたから。

togetter.com

恥ずかしながらStructual Subtypingって初めて聞きました。

このブログの説明読みました。

yuroyoro.hatenablog.com

「Structural Subtypingを用いることで静的なduck typingが可能になります」

なるほど!

という感じでした。

つまり、swiftで書くとしたら

protocol StringJoinable{
  func join(s1: String: s2: String): String
}

let s1 = "s1"
let s2 = "s2"
func joinStrings(joinable: StringJoinable){
     joinable.join(s1, s2)
}

//HogeクラスはStringJoinableを適用していないが、StringJoinableと同じメソッドを持っている
class Hoge{
  func join(s1: String: s2: String): String{
      return "Hoge: \(s1)\(s2)"
  }
}

joinStrings(Hoge())//これはもちろんコンパイルエラーだが、これがコンパイル通るということがStructural Subtypingができるといこと

という認識でいいのかな…