CORDEA blog

Android applications engineer

nim で json を扱う話

この記事は Nim Advent Calendar 2016 18 日目の記事です。

さて、ネタが被ったような気がしないでもないのですが
nim で json 扱うのが楽ですよって話をします。

json の扱いの簡単さはとても大切ですね。

今回例として使用した jsonjson.org の JSON Example の一番上のやつです。
http://json.org/example.html
この json を example.json として保存し、使用しています。

Version

Nim Compiler Version 0.15.2 (2016-12-18) [MacOSX: amd64]

json module を使う

json module を使えば json を簡単に扱うことができます

json -> JsonNode
import json

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    echo jsonObj["glossary"]["title"].str
  • Result
example glossary
JsonNode -> json
import json

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    echo $jsonObj
  • Result
{"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossL...

簡単ですが、用途によってはどこか物足りません。
やっぱり object に詰めて欲しいですよね。

そこで出てくるのが marshal module です。

marshal module を使う

marshal module を使えば json <-> object が実現できます
準備として example.nim として以下を用意しました

type
    Base = object of RootObj

    Example* = object of Base
        glossary*: Glossary

    Glossary* = object of Base
        title*: string
        GlossDiv*: GlossDiv

    GlossDiv* = object of Base
        title*: string
        GlossList*: GlossList

    GlossList* = object of Base
        GlossEntry*: GlossEntry

    GlossEntry* = object of Base
        ID*: string
        SortAs*: string
        GlossTerm*: string
        Acronym*: string
        Abbrev*: string
        GlossDef*: GlossDef
        GlossSee*: string

    GlossDef* = object of Base
        para*: string
        GlossSeeAlso*: seq[string]
json -> object
import example, marshal

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        ex = to[Example] jsonStr
    echo ex.glossary.title
  • Result
example glossary
object -> json
import example, marshal

when isMainModule:
    let
        ex = Example()
    echo $$ex
  • Result
{"glossary": {"title": null, "GlossDiv": {"title": null, "GlossL...

自分で object に詰める

さきほどの marshal module はとてもシンプルなもので
例えば json の key と object の key を変えたい場合などに対応することはできません。(できなかったはず...)

そのため、json の key に proc などがあるとつらい感じになります。
そういう場合は、自分で object に詰めてしまうのが楽だと思います。

今回の json は string と array しか出てこないので実装はこの程度でした

import json, sequtils

proc `[]`(t: JsonNode, key: string): JsonNode =
    result = if t.hasKey(key): json.`[]`(t, key) else: nil

proc to[T: object](node: JsonNode, data: var T) =
    for k, v in data.fieldPairs:
        when v is string:
            v = node[k].getStr
        elif v is seq:
            if node[k] == nil:
                v = @[]
            else:
                v = node[k].elems.map(proc(x: JsonNode): string = x.str)
        else:
            node[k].to v
  • Usage
import example, json, sequtils

...

when isMainModule:
    let
        jsonStr = "example.json".readFile()
        jsonObj = parseJson jsonStr
    var ex: Example
    jsonObj.to ex
    echo ex

実装も簡単ですし、依存も減るし、諸々の仕様に柔軟に対応できるので
状況次第では自分で実装してしまうのも悪くないかなと思っています。

それでは。

Jenkins の Android アプリ "Butler" を作った

Jenkins のクライアントアプリを作成しましたのでご報告。
もちろんオープンソースですが、今回ストアには公開していません。


github.com



ストアに公開しなかった理由についてですが、
アプリ的にではなく、API 的に公開できる品質にならなそうだと判断しました。
具体的には、ユーザー一覧など、多すぎるとタイムアウトしてしまう点です。
調べた限りではページング等できなさそうだったので、ここの改善は難しいと考えています。
そこらへんできる方法あれば教えていただけますと幸いです。

特徴

screen shot は GitHub をみてください。

アプリ的特徴

  • ビルド、プロジェクト、コンピューター、ユーザーの確認
  • ビルドの実行 (一部対応)

特徴

  • Kotlin
  • Data binding

まとめ

今買うの私的な挑戦は、Kotlin + Data binding の組み合わせを用いることで、
RxKotlin を使用しなかったことです。

Rx は流行りもあって広まっていますし、使用する人も増えていますが、
設計如何では、恐ろしいコードが生成される傾向があります。

そういうのをいくつかみてきたので、最近では Rx を使わない道を模索しており、今回のアプリはその一環です。
とはいえ、今回は MVVM を意識した作り (Data binding を使用しているので当たり前といえば当たり前ですが) なので
Rx を使ってもそれほど酷くはならなかったかもしれません。

あと、Kotlin は Observable とかあるので、使わなくても十分対応できるよねってところがあります。

Mackerel Client もまるごと書き直したい...


ストア公開する予定がないため、使いたい方はビルドして使ってみてください。apk もどこかに置く予定はあります。

Windows で perl5 の DBI モジュール使って嵌った話

perl5 の DBI モジュールを使用する際で
data_source の指定パスに日本語含まれてる場合に嵌った。


環境

>perl --version

This is perl 5, version 22, subversion 1 (v5.22.1) built for MSWin32-x64-multi-t
hread
(with 1 registered patch, see perl -V for more detail)

Copyright 1987-2015, Larry Wall

Binary build 2201 [299574] provided by ActiveState http://www.ActiveState.com
Built Jan  4 2016 12:12:58

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

結論

connect する際に utf8 にエンコードするといける
アプローチがあってるかどうかは謎

良いアプローチあったら教えてください


 

テスト

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use Encode qw/encode decode/;
use DBI;

my $file_enc = "cp932";

binmode(STDIN,":encoding($file_enc)");
binmode(STDOUT,":encoding($file_enc)");
binmode(STDERR,":encoding($file_enc)");

my $db_loc = $ARGV[0];

my $data_source = "dbi:SQLite:$db_loc";
my $dbh = DBI->connect($data_source);

$dbh->disconnect;
print "できた";
結果
>perl test.pl ほんとやめて\test.db
DBI connect('\x{0082}U\x{0082}n\x{0082}A\x{0082}a\x{0082}s\x{0082}A\test.db','',
...) failed: unable to open database file at test.pl line 21.
Can't call method "disconnect" on an undefined value at test.pl line 23.

修正

#!/usr/bin/perl
use strict;
use warnings;
use utf8;

use Encode qw/encode decode/;
use DBI;

my $file_enc = "cp932";

binmode(STDIN,":encoding($file_enc)");
binmode(STDOUT,":encoding($file_enc)");
binmode(STDERR,":encoding($file_enc)");

my $db_loc = $ARGV[0];

my $utf8_db_loc = decode($file_enc, $db_loc);
$utf8_db_loc = encode("utf8", $utf8_db_loc);

my $data_source = "dbi:SQLite:$utf8_db_loc";
my $dbh = DBI->connect($data_source);

$dbh->disconnect;
print "できた";
結果
>perl test.pl ほんとやめて\test.db
できた


python2 でこの類は慣れてるつもりだったけど
perl5 は格が違った
もう Windows + 日本語は本当につらい

Kotlin + OkHttp3 + Retrofit2 でヘッダの追加とか

Kotlin でアプリ書いてる時にちょっと戸惑った


 

OkHttpClient のヘッダーの追加は

val httpClient =
                    OkHttpClient.Builder()
                            .addInterceptor {
                                it.proceed(it.request()
                                        .newBuilder()
                                        .addHeader("Content-Type", "application/json")
                                        .build())
                            }
                            .build()


これを Retrofit で使用する場合

Retrofit.Builder()
                .baseUrl { HttpUrl.parse(url) }
                .client(httpClient)
                .build()
                .create(HogeHogeApiInterface::class.java, context)
                .postHuge()
                ...


Observable 返したい場合や、gson 使いたい場合
ここらへんは java と変わり無いですが

Retrofit.Builder()
                .baseUrl { HttpUrl.parse(url) }
                .client(httpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                ...

ちなみに HttpLoggingInterceptor 使う場合

val httpClient =
                    OkHttpClient.Builder()
                            .addInterceptor {
                                ...
                            }
                            .addInterceptor(okhttp3.logging.HttpLoggingInterceptor()
                            .setLevel(okhttp3.logging.HttpLoggingInterceptor.Level.BASIC))
                            .build()


実際に使ってるのはここらへんです。

もうちょっと知見が集まってくれると Kotlin 使う人は増えそうな気がする

Blackberry 10 での Json の扱い方について

Blackberry OS 10 のネイティブアプリ開発で Json を扱うことがあったのでメモ

C++Json を扱う場合、Cascades framework にある JsonDataAccess を用いるのが簡便だと思います。
Qt の対応バージョンが 4.8 で、Qt5.5? で追加された Json サポートを利用することができないためです。
使おうと思えば Qt5 も使えるようなので、その場合は Qt の Json サポートを利用するのがよいかもしれません。

qml で使用する場合は、JSON.parse() で行けるはず。

Parse

    QString jsonData = ....

    JsonDataAccess jda;
    QVariant var = jda.loadFromBuffer(jsonData);

    // map
    QVariantMap map = var.value<QVariantMap>();
    // list
    QVariantList list = var.value<QVariantList>();


レスポンスとして Json が返ってくる場合、このような使い方になります

QVariantMap ApiRequest::onRequestFinished(QNetworkReply* reply)
{
    if (reply && reply->error() == QNetworkReply::NoError) {
        const QByteArray buffer(reply->readAll());

        JsonDataAccess jda;
        QString jsonData = "hogehoge";
        QVariant var = jda.loadFromBuffer(jsonData);
        QVariantMap map =  var.value<QVariantMap>();
        ...
    }
}

 

Stringify

    QVariantMap map;
    map["hoge"] = "hogehoge";

    JsonDataAccess jda;

    QByteArray jsonString;
    jda.saveToBuffer(QVariant(map), &jsonString);


POST リクエストとか送る時はこんな感じ

void ApiRequest::request(QString username, QString password, QString factorKey)
{
    QNetworkRequest request;
    request.setUrl(QUrl("http://cordea.jp"));

    connect(mManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*)));

    QVariantMap map;
    map["hoge"] = "hogehoge";

    JsonDataAccess jda;
    QByteArray body;
    jda.saveToBuffer(QVariant(map), &body);

    mManager->post(request, body);
}


c++ 的に書き方があっているかは若干不安。


F# で Android アプリ開発いいよねって話を今更。

Xamarin と F# 使って Android アプリ開発の話。

最近 F# とか blackberry とかやってて完全に変人扱いされています。
ですが、Rx とか流行ってるし、次にくるのは F# だと信じています。


Android Java or Kotlin はもちろん、
Xamarin C# に比べても人口も情報も圧倒的に少ないですが
でも IDE があって、サポートされているだけでも上々ですよね。

F# のよさを語るよりも実際にコードを見ていただいたほうが魅力が伝わるかと思いますので、
おなじような実装を Java, C#, F# でしてみました。

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    ListView listView = (ListView) findViewById(R.id.list_view);
    listView.setAdapter(adapter);

    Context context = this;
    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent(context, TestActivity.class);
            startActivity(intent);
        }
    });
}

C#

protected override void OnCreate (Bundle bundle)
{
    base.OnCreate (bundle);
    SetContentView (Resource.Layout.FirstView);

    var toolbar = FindViewById<Toolbar> (Resource.Id.toolbar);
    SetSupportActionBar (toolbar);

    var listView = FindViewById<ListView> (Resource.Id.list_view);
    listView.Adapter = adapter;

    FloatingActionButton fab = FindViewById<FloatingActionButton> (Resource.Id.fab);
    fab.Click += (sender, e) => {
        StartActivity (typeof(TestActivity));
    };
}

F#

override me.OnCreate (bundle) =
    base.OnCreate bundle
    me.SetContentView Resource_Layout.Main
    
    let toolbar = me.FindViewById<Toolbar> Resource_Id.toolbar
    me.SetSupportActionBar toolbar
   
    let listView = me.FindViewById<ListView> Resource_Id.list_view
    listView.Adapter <- me.adapter
    
    let onClick e =
        let intent = new Intent(me, typeof<TestActivity>)
        me.StartActivity intent
        
    let fab = me.FindViewById<FloatingActionButton> Resource_Id.fab
    
    fab.Click.Add(onClick)


どうでしょうか。
個人的には brace ないだけでもかなり見通し良いと思っています。

Xamarin を使用されている方もいない方も、ぜひ一度 F# を。



F# でもうちょっとちゃんと実装してあるものはこちらのリポジトリにあります。
github.com
blackberryiOS なども突っ込まれててカラフル。

その Activity 、少し長すぎませんか?

Android の Activity や、layout xml, 気をつけていても長くなってしまう場合があります。

そして、特に一人で開発していたりすると、
長すぎることに気づいてもついつい放置してしまいがちです。

ただ、放置すると、多くの場合引継ぎ等に支障をきたします。
layout xml とかあまりに長すぎると補完聞かなくなったりしますしね...

というわけで、
今回は java file や layout xml が指定行数を超えるとビルドが通らなくなる方法を検討してみました。


checkFileLimits() をビルドの際に呼び出すことで
指定行数を超えるとビルドが通らなくなります。

import groovy.io.FileType

class LineLimit {
    void checkFileLimits(base, layoutLimit, javaLimit) {
        def layout = base + "/src/main/res/layout/"
        def java = base + "/src/main/java/"
        checkLimit(layout, layoutLimit)
        checkLimit(java, javaLimit)
    }

    void checkLimit(dir, limit) {
        new File(dir).eachFileRecurse(FileType.FILES) {
            def c = it.text.split("\n").size()
            if (c >= limit) {
                throw new Exception("File exceeds the limit. Number of lines: " + c + ", File name: " + it.name)
            }
        }
    }
}


 

    buildTypes {
        debug {
            def lineLimit = new File(projectDir.absolutePath + "/LineLimit.groovy")
            def sc = new GroovyClassLoader().parseClass(lineLimit)
            def llClass = sc.newInstance()
            llClass.checkFileLimits(projectDir.absolutePath, 499, 999)
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }


 

method の呼び出しが美しくないので
ここらへんは groovy, gradle に明るい方に教えていただきたい