buto > /dev/null

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

React Nativeを始めてみた

やっとReact Native始めました

夏頃からReact Nativeやるぞ〜と言って早数ヶ月…すっかり寒くなってしまった

今日やっとReact Nativeに入門しました!!

React NativeはJavaScriptフレームワークReactのモバイル版で、1つのコードでiOSAndroidどちらでも動くクロスプラットフォームが実現できる!

(Reactやったことないんだが、大丈夫かな)

開発環境を構築する

React Native環境構築手順

このQiita記事の通りにやっただけですが、書いていきます

また、今回はAndroid環境のみです

  • macOS

  • nodev12.18.2

  • npm6.14.5

  • AndroidStudio

  • OPPO R15 Neo

React Nativeをインストール

既にNode.js、npmはインストール済みだったので

Homebrewでファイルのバージョン管理ライブラリ?のwatchmanをインストール

(Qiitaに書いてあったからインストールしました これはなくても良いのかも)

brew update
brew install watchman

npmでReact Nativeをインストール

npm install -g react-native-cli

AndroidStudioの設定

私の環境ではOreoのみ入っていたので、QiitaにしたがってMarshmallowを追加しました

Google APIなどはデフォルトで入っているようだったので特に設定していません

あとはAndroidSDKにパスを通すだけ

vi ~/.bash_profile

# .bash_profileの末尾に以下を追加して保存
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/platform-tools

React Nativeプロジェクトを作成

あとはコマンドでプロジェクトを作成して実行するだけ!

以下のコマンドでReact Nativeプロジェクトを作成します

# 事前にプロジェクトを作るディレクトリに移動しておく
react-native init プロジェクト名

React Nativeプロジェクト実行!

プロジェクトが作成されたら、AndroidStudioを起動した状態でスマホを接続

スマホのUSBデバッグを有効にしておきます

AndroidStudio画面上部に接続したスマホが表示されていればOK f:id:butorisa:20201107190813p:plain

それでは実行!

cd プロジェクト名
react-native run-android

ん!?openjdkの開発元を検証できないため開ません!?

システム環境設定-セキュリティ-一般からopenjdkを許可すればいいみたい

これで再度react-native run-androidを実行するとプロジェクトのビルドが成功!

スマホにこんな画面が表示されました f:id:butorisa:20201107191757p:plain

Kotlin入門 おみくじアプリを作ってみた

簡単なアプリを作りました

アプリを起動時 ボタンを押すとおみくじ結果画面に遷移します

f:id:butorisa:20201020175453p:plain

おみくじ結果画面 3秒ほど「Fortune Telling...」と表示され、結果が表示されます

f:id:butorisa:20201020175519p:plain f:id:butorisa:20201020175528p:plain

githubで公開しています Kotlin学習にどうぞ! https://github.com/butorisa/fortune-app

※画像には表示されていませんが、利用しているAPIのリンク・占い提供元のリンクを3枚目画面の下部に表示するようにしました

アプリの概要

  • 開発端末OS:Android 8.1.0(Oreo)
  • 利用APIWeb ad Fortune 無料版API
  • HTTP通信ライブラリ:OkHttp3

  • MainActivity

    • ActionBar(正確にはToolBar)のみ
    • 画面部分はFragmentで描画
  • EntryFragment
    • 起動時の画面(1枚目)
    • MainActivityの上から描画
    • FortuneResultFragmentに遷移するボタンを保持
  • FortuneResultFragment
    • おみくじ結果描画画面(2、3枚目)
    • 画面描画時に占いAPIを実行して、結果の12星座運勢からランダムな結果を表示
    • API実行はCoroutineで非同期実行
    • おみくじ結果が表示されるまで[GO BACK]ボタンは動作しない
    • ボタン押下で前画面へ戻る(EntryFragment指定はしていない)
  • FortuneResultBean
  • RestApiClient
    • 占いAPI呼び出しクラス(バックエンド)
    • 引数にURLを取り、APIを実行、レスポンスjson(String型)を返す
    • BeanクラスへのマッピングはRestApiClient呼び出し元で行う

MainActivity

MainActivityはActionBarしかない空っぽの画面なのでEntryFragmentを表示するだけ アプリテーマを「NoActionBar」にしているのでActionBarの設定コードを書いている

    /**
     * Activity描画
     */
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // actionbar
        val toolbar = findViewById<Toolbar>(R.id.toolbar_fortune)
        toolbar.title = "pretty chipper"
        setSupportActionBar(toolbar)

        // EntryFragment描画
        val transaction = supportFragmentManager.beginTransaction()
        transaction.addToBackStack(null)
        transaction.replace(R.id.container, EntryFragment())
        transaction.commit()
    }

EntryFragment

inflater.inflate() でFragmentのxmlファイルをバインド ボタンに押下時の処理を設定

    /**
     * EntryFragment描画
     */
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        val view = inflater.inflate(R.layout.fragment_entry, container, false)

        // ボタン押下でFortuneResultFragmentに移動
        view?.findViewById<Button>(R.id.button_telling_fortune)?.setOnClickListener {
            val transaction = fragmentManager?.beginTransaction()
            transaction?.addToBackStack(null)
            transaction?.replace(R.id.container, FortuneResultFragment())
            transaction?.commit()
        }
        return view
    }

FortuneResultFragment

onCreateView()で画面描画時にCoroutineを使って非同期で占いAPIを実行 APIのURLには「yyyy/MM/dd」形式で現在日付をつけると本日の占い結果が返却される APIレスポンスが返ってきたら、TextViewに占い結果を設定 レスポンスjsonを全てgetJSONObject()で取得するのは面倒なので、12星座の運勢のレスポンス部分だけBeanクラスを作りマッピング

    /**
     * FortuneResultFragment描画
     */
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        super.onCreateView(inflater, container, savedInstanceState)
        val view = inflater.inflate(R.layout.fragment_fortune_result, container, false)

        /* Coroutine start */
        this.lifecycleScope.launch {
            // call API
            withContext(Dispatchers.Default) {
                val url = "http://api.jugemkey.jp/api/horoscope/free/" + getCurrentDate()
                RestApiClient().requestGet(url)
            }.let {
                delay(2000)
                // mapping json to bean
                val fortuneResult = parseJson(getCurrentDate(), it)
                // set TextView
                view?.findViewById<TextView>(R.id.fortune_content)?.text =
                    fortuneResult.get((0..11).random())?.content // TODO ランダムな星座を表示

                // ボタン押下で前画面へ移動
                view.findViewById<Button>(R.id.button_back_to_entry).setOnClickListener {
                    fragmentManager?.popBackStack()
                }
            }
        }
        /* Coroutine end */
        return view
    }

    /**
     * 現在日付取得
     * @return 現在日付(yyyy/MM/dd)
     */
    @RequiresApi(Build.VERSION_CODES.O)
    fun getCurrentDate(): String {
        val today = LocalDate.now()
        val dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")
        return dateFormatter.format(today)
    }

    /**
     * json→FortuneResultBean変換
     * @param today:現在日付(yyyy/MM/dd)
     * @param strJson:レスポンスjson
     * @return 運勢一覧(FortuneResultBeanリスト)
     */
    private fun parseJson(today: String, strJson: String): List<FortuneResultBean> {
        val json = JSONObject(strJson)
        // remove "\" from json-property
        val strResult = json.getJSONObject("horoscope").getString(today.replace("\\", ""))
        return ObjectMapper().readValue(strResult, object: TypeReference<List<FortuneResultBean>>(){})
    }

FortuneResultBean

占いAPIの運勢部分のjsonマッピングするBeanクラス このBeanクラスを12星座分のリストにして受け取る

/**
 * 占いAPIレスポンスBean
 */
class FortuneResultBean {
    @JsonProperty("content")
    var content: String? = null

    @JsonProperty("money")
    var money: String? = null

    @JsonProperty("job")
    var job: String? = null

    @JsonProperty("love")
    var love: String? = null

    @JsonProperty("total")
    var total: String? = null

    @JsonProperty("item")
    var item: String? = null

    @JsonProperty("color")
    var color: String? = null

    @JsonProperty("day")
    var day: String? = null

    @JsonProperty("rank")
    var rank: String? = null

    @JsonProperty("sign")
    var sign: String? = null
}

RestApiClient

OkHttp3を利用してGETリクエスト レスポンスボディをString型のまま呼び出し元へ返す

    /**
     * GETリクエスト
     * @param APIリクエストURL
     * @param リクエストパラメータ
     * @return APIレスポンス(String)
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    fun requestGet(url: String, vararg param: String?): String {
        val request = Request.Builder().url(url!!).build()
        val httpClient = OkHttpClient()
        try {
            val response = httpClient.newCall(request).execute()
            return response.body!!.string()

        } catch (e: IOException) {
            // TODO handle exception
            e.printStackTrace()
        }
        // TODO Handle Exception
        throw NullPointerException()
    }

    companion object {
        private val JSON: MediaType = "application/json; charset=utf-8".toMediaTypeOrNull() as MediaType
    }

きれいに書くよう意識しました

今までとりあえず動くことだけ意識していたのですが、後で見返すと分かりづらい。。。 FragmentにあるボタンのクリックリスナーをActivityに書いていたり、API呼び出し関数にUI操作を書いていたり、不要な変数を宣言していたり… このあたりをスッキリするようにコーディングしてみました Androidのコード設計、デザインパターンも意識しながらコーディングできるようになりたい!

Docker ElasticSearchでPDF全文検索

ElasticSearchイメージでサクッと全文検索してみる!!

ElasticSearch環境構築

  • 環境
#elasticsearchイメージをダウンロード
docker pull elasticsearch
# コンテナ作成・起動
docker run -it -d --name elastic_search -p 9200:9200 -p 9300:9300 -v C:¥Users¥buto¥share:/mnt/share elasticsearch

全文検索に使うプラグインをインストール

# コンテナに入る
docker exec -it elastic_search /bin/bash

###### ここからコンテナ環境 ######

# ログイン直後はrootユーザー
su - elasticsearch
# elasticsearch-pluginコマンドでプラグイン操作
elasticsearch-plugin install kuromoji # 日本語対応
elasticsearch-plugin install analysis-icu # 文字の解析器
elasticsearch-plugin install ingest-attachment # ESにファイルを取り込む
# プラグインが追加された
elasticsearch-plugin list
# analysis-icu
# analysis-kuromoji
# ingest-attachment

プラグインをElasticSearchに反映させるためにコンテナ再起動(Docker for Windows GUIから実行) ※ コンテナ内でsudo systemctl elasticsearch restartできなかった。。。

PDFを取り込むパイプラインを作成

以下のリクエストでPDFをElasticSearchに流すパイプラインを作成する

curl --location --request PUT 'http://localhost:9200/_ingest/pipeline/attachment'
--header 'Content-type: application/json'
--data-raw '{
  "description" : "sample mapping",
  "processors" : [
    {
      "attachment" : {
        "field" : "data",
        "indexed_chars" : -1,
        "properties" : [
         "content",
         "content_type"
        ]
      }
    }
  ]
}'

attachmentという名前のパイプラインができる!

文字解析器を作成

PDFの文章を2~3文字ずつ区切って解析するよう設定

curl --location --request PUT 'http://localhost:9200/ngram_1'
--header 'Content-type: application/json'
--data-raw '{
    "settings": {
        "analysis": {
            "analyzer": {
                "my_ja_analyzer": {
                    "type": "custom",
                    "tokenizer": "my_tokenizer",
                    "char_filter": [
                        "icu_normalizer",
                        "kuromoji_iteration_mark"
                    ],
                    "filter": [
                        "kuromoji_baseform",
                        "kuromoji_part_of_speech",
                        "ja_stop",
                        "kuromoji_number",
                        "kuromoji_stemmer"
                    ]
                }
            },
            "tokenizer": {
                "my_tokenizer": {
                    "type": "ngram",
                    "min_gram": 2,
                    "max_gram": 3,
                    "token_chars": [
                        "letter",
                        "digit"
                    ]
                }
            }
        }
    },
    "mappings": {
        "properties": {
            "attachment.content": {
                "type": "text",
                "analyzer": "my_ja_analyzer",
                "search_analyzer": "my_ja_analyzer"
            }
        }
    }
}'

ngram_1という名前の解析器が作成される

PDFデータをBase64エンコード

HTTPリクエストボディでPDFをパイプラインに送るため、PDFファイルをBase64エンコードする ElasticSearchのVMLinux)で以下のコマンドを実行

base64 -w 0 sample.pdf > sample.txt

sample.txtにBase64エンコードした内容が出力される

PDFをパイプラインに投入

curl --location --request POST 'http://localhost:9200/ngram_1/_doc/1?pipeline=attachment'
--header 'Content-type: application/json'
--data-raw '{
    "index": {
        "_index": "ngram",
        "_type": "pdffile",
        "_id": "1",
        "pipeline": "attachment"
    },
    "data": "sample.txtの中身"
}'

これでElasticSearchにPDFの文章が取り込まれる!

文字列検索してみる

GETリクエストのqパラメータに検索したい文字列をセットする

curl --location --request GET 'http://localhost:9200/ngram_1/_search?q=検索ワード'

ヒットするとこんなレスポンスが返ってきます

{
    "took": 34,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 2.7496266,
        "hits": [
            {
                "_index": "ngram_1",
                "_type": "_doc",
                "_id": "1",
                "_score": 2.7496266,
                "_source": {
                    "data": "sample.txtの中身",
                    "attachment": {
                        "content_type": "application/pdf",
                        "content": "sample.txtをBase64デコードした内容"
                    },
                    "index": {
                        "pipeline": "attachment",
                        "_index": "ngram",
                        "_type": "pdffile",
                        "_id": "1"
                    }
                }
            }
        ]
    }
}

ヒットしなかった場合はこんなレスポンス

{

    "took": 6,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 0,
            "relation": "eq"
        },
        "max_score": null,
        "hits": []
    }
}

やってみて

ElasticSearchのDockerイメージがあったので構築は楽でした! PDFを取り込むためのパイプライン、解析器の作成などが理解できず時間が掛かりました この設定で大体の日本語はヒットするのですが、ヒットするべきでないワードでもヒットしてしまうことが多々ありました… AWSのElasticsearchServiceもありますが、あちらはanalysis-icuがサポートされていなかったので、ヒットしてほしい時にヒットできないことがありました(AWSの方はプラグイン追加はできない) 全文検索をちょっと試してみたい時にどうぞ!!

Ruby インクリメント演算子が使えない理由

昨日、夜中の空腹をまぎらわせるためにRubyをインストールしてみました 今朝から制御構文などの基礎コードを書いていたのですが、num++のような インクリメント演算子が使えないことが分かり「なんで?」と気になったので調べました

理由:Rubyでは数値もオブジェクトだから

こちらのブログで分かりました! Ruby にインクリメント演算子のようなものが無い理由

インクリメント演算子が利用できない理由をRuby開発者が以下のように語っています

[ruby-list:5323] Re: Questions on specs and threads

3) 記号的な記法
これは単なる私の趣味ですが, 単項インクリメントとかがたまに欲しく
なります. i += 1 でいいわけですが. i++ と書いて怒られる (^^;

すんません.この件は以前から指摘されているのですが(演算子はC に似ているのに++と--は対応する演算子が無い),++の動作が本質 的に「変数を操作する」ものであるため,変数がオブジェクトでな いRubyでは導入できないでいます.++や--の「オブジェクト指向的 意味」がRubyの他の部分と整合性を保ったまま定義できれば採用し たいのですが….

Rubyは変数の操作をさせたくない言語なんですね 「変数がオブジェクトでない」ってどういうことなんだろう??

Rubyでは変数がオブジェクトでない」とは

【Ruby公式サイト】JavaからRubyへより

Javaと違って、Rubyは… 2や3.14159といった数値も含めて、すべてのものはオブジェクトです。

変数numは(numそのものが)オブジェクトではなくて、数値オブジェクトへの参照 → 1つの数値オブジェクトを共有している

# 変数numには123そのものではなく
# 123へのアドレスが格納されている

num = 123
puts num.object_id # 247が出力される

num_cp = 123
puts num_cp.object_id # 247が出力される

変数はオブジェクトへの参照だから、「変数自体がオブジェクトでない」ってことなんですね

数値オブジェクトはimmutable

上記コードのようにnumnum_cpは1つの数値オブジェクトを参照している → 数値オブジェクト「123(object_id:247)」自体が操作されたら、   参照している変数の値が全部変わっちゃう!!

なので、数値オブジェクトはimmutable(不変、final)になっている 以下のコードは変数の値を操作しているように見えるが、参照先を付け替えているだけ

cnt = 123
puts cnt.object_id # 247が出力される

cnt = 456
puts cnt.object_id # 913が出力される

参照している数値オブジェクトを付け替えることによって数値オブジェクト自体は操作されずに 変数の値が操作されているように見える!

インクリメント演算子は数値オブジェクトの操作を意味する

もう一度、最初に引用した開発者のコメントを見てみます

++の動作が本質的に「変数を操作する」ものであるため,変数がオブジェクトでな いRubyでは導入できないでいます.

インクリメント演算子は数値オブジェクト自体に加算することを意味するので、 Rubyの「数値オブジェクトはimmutable」のルールと合わなくてサポートされていない

言語って奥が深い!!

Rubyでインクリメント演算子がサポートされていないことにもちゃんと理由があった! Pythonも同じ理由でインクリメント演算子をサポートしていないらしい(使えると勘違いしていた)

このテーマとは違いますが、調べている時に「各言語のメモリの使い方」の解説に遭遇して とても面白かったです メモリとスタックとヒープとプログラミング言語

Haskell VSCodeで実行できない

前回の記事HaskellIDEでコーディングするのに必要なツールが分かったので、早速試してみる!!

  • OS:Windows10

StackからGHCをインストール

  1. Stackをインストール
  2. StackからGHCをインストール
    • stack setup
  3. GHCがインストールできたことを確認
    • stack ghc -- --version
      • コンパイラがインストールできた!
      • The Glorious Glasgow Haskell Compilation System, version 8.8.3

haskell-ide-engineをインストール

  1. コマンドプロンプト文字コードUTF-8にしておく
    • chcp 65001
  2. haskell-ide-engine(HIE)をインストール
    • あらかじめGitをインストールしておく
    • cd クローンするフォルダ
    • git clone https://github.com/haskell/haskell-ide-engine
    • cd haskell-ide-engine
    • stack ./install.hs hie
      • Version 1.4, Git revision e4972ff44c7649e3f53ffac37ae899410075aa0f (3903 commits) x86_64 ghc-8.8.3
    • stack ./install.hs data

stack installではなくstack ./install.hsを使うのがポイントらしい (StackのinstallコマンドではなくHIEのinstall.hsを使う)

VisutalStudioCodeをインストール

intelliJだとstack.exeを認識してくれなかったのでVSCodeでやってみる

  1. VSCodeインストーラをダウンロードしてインストール
  2. インストール後にVSCodeを起動して[Extensions]からプラグインを導入
  3. setting.jsonにHIEのパスを設定
    • "haskell.serverExecutablePath": "~/hie/haskell-ide-engine"

これでVSCodeを再起動するとHaskellのビルド・実行ができるはずだった…

f:id:butorisa:20201020174913j:plain

あれ?Haskell実行できない??? (2時間経過) 一旦、コマンドら実行でいいや

Haskell 開発環境に必要なツール

HaskellIDEでコーディングしたい

最近またHaskellに興味がわいてきて、再び入門することにしました これまではAtomなどのテキストエディタでコードを書いて、コマンドでコンパイル・実行していました 他の言語ではIDEを使っているのでHaskellIDEで開発できるようにしたい!

この記事ではHaskell開発環境に必要なツールのまとめです

※正確にはライブラリ、プラグイン…などの言い方があると思いますが、まとめてツールと言っています

Haskellの実行に必要なツール

Haskellコードを書いてコンパイル・実行するためには以下が必要です - GHC(Glasgow Haskell Compiler) - Haskellコンパイラ - グラスゴー大学の先生?が開発したコンパイラで最もメジャーなHaskellコンパイラ - コンパイラのブランドって意識したことなかった。。。

コマンドでコンパイル・実行する時はGHCだけあればOK!

プロジェクト作成・ビルドに必要なツール

Cabalというビルド・パッケージ管理ツールもありますが、初心者にはStackがおすすめと紹介されていたので私はStackを使います CabalとStackの違いは何ですか?

StackはCabalライブラリを使っているようです Cabalを使っているとcabal hell(カバル地獄)という恐ろしい事象があるみたい

IDEを使った開発に必要なツール

【AWS】HTTPS化のアレコレ

うわぁ!やっちゃった!

今朝、社内でテストなどに使っているAWS CloudWatchからコスト超過のSlackが来ました。 まだ8月に入ったばかりです。やばい、と会社の先輩と原因を調べてみると、

CertificateManager $386.85

なんかめっちゃ高い項目ある!なんだろ… あ、これ先月に私が作ったプライベートCAだ

私がプライベートCAを立てた経緯

工数管理・トレーサビリティ向上のために社内AWS環境にRedmineを構築(bitnami AMI) → https化自己署名証明書)を依頼される → 「AWS https化」で検索、ネット記事の手順をそのまま実施 → プライベートCAを作成

Redmineは社内でしか使わない、セキュリティグループもそのように設定しているので プライベートCAを立てずに自己署名証明書のみで良かった!(ていうか、そう依頼されていた)

よく分からないままエイヤでやってしまった

根本的な原因はこれです。https化自己署名証明書の仕組みを理解せず作業してしまいました。 なので、今勉強しておきます!

HTTPS化とは

HTTPSとは 安定のピヨ太くん分かりやすいですね

HTTPだと通信の内容が見られてしまう危険性があるため、 暗号化通信のSSLがセットになったHTTPSを使おう!ということ

HTTPSには「サーバ証明書」が必要(暗号化する鍵)

  • お客様が見るサイトはパブリック認証局が発行するサーバ証明書を使う
  • 社内向けサイトはプライベート認証局(プライベートCA)が発行するサーバ証明書を使う
    • プライベートCAの証明書のみを信頼できるルートとすると、発行した証明書が信頼性のあるものになる
  • 自分しか見られない、暗号化するだけが目的だったらサーバ自身が発行する自己署名証明書

AWSでのHTTPS通信

【初心者向け】AWSのサービスを使ってWebサーバーをHTTPS化する

AWSでのHTTPS通信を実現するには以下2通りの方法がある

  1. EC2に証明書をインストール
  2. ELBに証明書をインストール

何か違いはあるのかな?? → EC2上のWebサーバなどがHTTPS非対応だったらELBに証明書をインストールして   ELB-EC2間はHTTP通信にする

結論:脳死状態でAWS触らない

自分が何をしているのかハッキリ分かっていない状態での環境構築は控えましょう 糖分をとって分からないことは調べて聞いて目的と手順をきちんと理解して作業しましょう そして会社のみなさんごめんなさい!!