2015年12月9日水曜日

開発業務のチーム内チャットツールとしてZulipを導入してみた感想

最近Dropboxがオープンソースとして公開した「Zulip」というチャットアプリ(サーバとクライアント)を社内に試験導入してみました。

もともとIRCを使っていたのですが、周知したいメッセージを送ったのに誰かが接続されてなくて「それ知らない」が発生したり、過去ログがなくて「あのときどう言ったっけ?」みたいなのを探せなかったりして不満があったからです。

ざっくり使ってみた感想としては、悪くもなく、良くもなく、といったところです。

サーバ構築

Ubuntu 14.04.3を仮想サーバとして用意しました。
その上で公式の手順をコピペでセットアップしました。

すると5番目の黒四角「su zulip -c /home/zulip/deployments/current/scripts/setup/initialize-database」で失敗しました。

下の説明を読むと「Zulip in production READMEを読んで/etc/zulip/settings.py」を編集しろ、と。
以下のように編集しました。※カッコは設定値の例
シングルサインオンは使わず、メールサーバも暗号化なしの25番ポートです。

  • EXTERNAL_HOSTにホスト名をFQDNで(zulip.myoffice.co.jp)
  • ZULIP_ADMINISTRATORに自分のメールアドレス(hypermiod@myoffice.co.jp)
    ※後述しますが、管理者用のメールアドレスを用意しておく方が良いかもしれません。
  • ADMIN_DOMAINにドメイン?(myoffice.co.jp)
  • AUTHENTICATION_BACKENDSは先頭のEmailAuthBackendの行をコメントアウト
  • EMAIL_HOSTには社内のメールサーバをFQDNで(smtp.myoffice.co.jp)
  • EMAIL_PORT = 25
  • EMAIL_USE_TLS = False
  • DEFAULT_FROM_EMAILは適当にドメイン部分を置き換え(Zulip <zulip@zulip.myoffice.co.jp>)
  • NOREPLY_EMAIL_ADDRESSも同様(noreply@zulip.myoffice.co.jp)
なお最初ERROR_REPORTINGをFalseにしておくのを忘れてしまいましたが、後から変更できました。

設定を変更してから再度5番目の「initialize-database」を実行するとうまくいき、続いて最後の「su zulip -c /home/zulip/deployments/current/scripts/restart-server」で起動しました。

ユーザ登録

Chromeで「http://zulip.myoffice.co.jp/」へアクセスすると、自動的にhttpsへ転送されます。
証明書の警告画面を強行突破すれば、ログイン画面が表示されます。

Registerへ進み、ZULIP_ADMINISTRATORに設定したアドレスで登録すると、「Activate your Zulip account」というタイトルの確認メールが届くので、本文中のリンクからアクティベートします。

これでログインできました。

ぱっと見の印象は「分かりづらい」でした。
英語と言うこともありますが、画面に見えている要素が何なのか知識がなかったからです。

要素

  • ストリーム(Stream)
    チャットのグループです。購読(Subscribe)できるユーザを制限できたりします。
    基本的には管理者がプロジェクトごとなどの単位で作成しておくと良いでしょう。
     
  • トピック(Topic)
    ストリームの中で会話を分けるためのものです。
    画面左のストリームを開くと、トピックの一覧が表示されます。
    トピックを削除することはできません。ミュート(Mute)することはできます。
    ただトピックへの投稿が減れば一覧末尾の(more topics)へ押しやられるので普段目にすることはなくなります。

初期設定

右上の歯車メニューから色々と変更しました。

  • Manager Streams
    チーム共通、プロジェクトごとのストリームを作りました。一応すべてPrivateとし、メンバを制限しています。
     
  • Settings
    Display Settingsで24時間表記にしました。
     
  • Administration
    Your organization's nameを「○○チーム」と変更しました。
    ブラウザのタイトルに反映されます。

気づいたことなど

  • 最初は自分が管理者です。「Administration」設定で他のユーザも管理者にできます。
     
  • 管理者のみに有効な項目は一般ユーザには見えません。
    当然と言えばそうなのですが、管理者からするとどの項目が一般なのか分かりづらいです。事前に管理者専用のメールアドレスを作成しておいて、常用するメールアドレスをそのまま管理者としないほうが良い気がします。 
     
  • IEはダメなようです。IE11だとログイン後の画面でずっと「Loading」が終わりません。
     
  • 後から参加したユーザは、それまでのやりとりが見えないようです。つまりStreamに「使い方」みたいな投稿をしても、肝心な人に届きません。
     
  • 検索がダメっぽいです。日本語だと何も見つかりませんし、英語も見つかったり見つからなかったり。というか"topic"や"stream"でもBotのメッセージしか見つかりません。
     
  • ブラウザではなくWindowsやMacのクライアントもあるのですが、動作しませんでした。インストール後起動時にドメインを指定しろ、とあるので「zulip.myoffice.co.jp」を指定したのですがいつまでも画面が真っ白でした。
    自己証明書だからか、とこのページにあった--allow-insecureオプションも指定してみましたが変わらず。
というわけで「なんかイマイチ」感が漂いますが、そのうち良くなるかなと思いながら使ってみることになりました。できればコントリビュートとかしたいのですが時間が・・・。


2015年11月27日金曜日

build.gradleの依存ライブラリのバージョンが勝手に変更されてしまう

Android Studioでアプリ開発をしています。

アプリのbuild.gradleとライブラリのbuild.gradleでそれぞれdependenciesに

compile 'com.google.android.gms:play-services-maps:8.1.0'

と書いているのですが、気づくとアプリ側だけ

compile 'com.google.android.gms:play-services-maps:8.3.0'

と勝手に書き換わっています。

このままではそれぞれに含まれるバージョンが違うせいか実行しようとすると

AGPBI: {"kind":"simple","text":"com.android.dex.DexException: Multiple dex files define Lcom/google/android/gms/ads/identifier/AdvertisingIdClient$Info;","sources":[{}]}

というエラーが表示されてしまいます。

(adsで問題がでているのは、おそらくライブラリ側でanalyticsを依存に書いていて、それがadsに依存しているから)

これは最新版を使えという思し召しなのでしょうか・・・なぜ片方だけ・・・謎です。

古いアプリを移行してきたので当面はバージョンを変えたくないのですが。

2015年11月7日土曜日

[解決]Playストアで「サーバーからの情報の取得中にエラーが発生しました」が出るときの対策

先日、古いAndroidタブレットを初期化したら使い物にならなくなったことで大変困りました。

これが意外なことで解決したのでご報告します。

やったことはズバリ『「Google Play開発者サービス」を強制的に上書きインストール(更新)すること』です。

「Google Play開発者サービス」のアプリ本体(APK)を取得できさえすれば後は簡単です。

手順は以下の通り。


  1. WebブラウザでAPK Downloader(http://apps.evozi.com/apk-downloader/)を開く
  2. 先頭の入力欄を1回クリックする
  3. ↑で別のタブが開いたり別のページへ遷移したら戻る(ただの広告です)
  4. 再び入力欄をクリックして「com.google.android.gms」と入力する
  5. すぐ下の「Generate Download Link」を押す
  6. 「Click here to download com.google.android.gms now」ボタンが表示されたら押してAPKをダウンロードする
  7. なんとかしてAndroid端末へAPKファイルを転送する
  8. Android端末でAPKファイルを開いてインストールする

5まで実行したときの画面はこんな感じです。

7については、私はBluetoothで転送しました。
メールに添付したり、microSDカードなどを使ったりするのも良いと思います。

ページ内の「QR Code」の「View」を押すと別タブでQRコードが開くので、おそらくそこからダウンロードできるかと思います。Android端末にQRコードリーダが付いている場合これが一番簡単かもしれません。

「Google Play開発者サービス」さえ更新してしまえば、Playストアも普通に使えるようになりました。

お困りの方は、是非試してみてください。

2015年10月28日水曜日

[解決]60秒以上かかる定期処理にWakefulBroadcastReceiverは使えない

Androidで定期的にバックグラウンド処理する場合、従来はPARTIAL_WAKE_LOCKなWakeLockを使う必要がありました。

AlarmManagerでタイマーを仕掛け、タイマーで起こされたBroadcastReceiverがWakeLockでCPUをスリープしないようにしてからServiceを呼んで、Serviceでバックグラウンド処理をし、完了したらBroadcastReceiverが持っているWakeLockを解除してCPUがスリープできるようにする、という流れです。

ここでWakeLockが若干面倒なので処理を簡素化するためにできたのがWakefulBroadcastReceiverです。

公式のトレーニングにも説明が載っています。

WakefulBroadcastReceiverからstartWakefulService()でServiceを起動し、Serviceは処理が済んだらWakefulBroadcastReceiver.completeWakefulIntent()を呼ぶ。
その間はCPUがスリープしないと。

完璧です。

そう思って僕も早速これに置き換えましたが、どうもServiceの処理に時間がかかってしまいます。

もともと2分くらいなのですが、10分とか1時間とかかかっているケースもあります。ただ充電中(開発者オプションで画面付けっぱなし)は2分くらいで済んでいます。

どうも、CPUがスリープしてしまっているように思えます。が、そんなハズはとWakefulBroadcastReceiverのソースを読んでみたところ・・・

wl.acquire(60*1000);

60秒でタイムアウトだそうで。

今回のケースではアウトです。
本当にありがとうございました。

--- 20151029追記 ---

今回はWakefulBroadcastReceiverを取り込んで対処しました。
ソースを自分のプロジェクトにコピペしてpackageとタイムアウト値を変更して使いました。
Apache 2.0なライセンスなので、そこら辺はちゃんとしないといけないですね。

2015年10月27日火曜日

[解決]BOOT_COMPLETEDで起動しない機種があるときはintent-filterに誤りがあるかも

AndroidManifestにこう書いていたら、Zenfone5の電源投入時にonReceiveが呼ばれませんでした。

        <receiver android:name=".BootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>

カテゴリ指定を取ってこう書いたらonReceiveが呼ばれるようになりました。

        <receiver android:name=".BootReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

公式のトレーニングにもそう書いてありますね。

しかも最初は呼ばれない(enabled="false")ようにして、ユーザが明示的にONしたときにPackageManager経由で有効にする、というのが正しいやり方のようです。

・・・というか、このやり方なら「電源投入時に起動する」という設定を覚えておく必要がないのですね。

2015年7月29日水曜日

Windows 10をインストールして戸惑ったこと&つまづいたこと

Windows 10を早く試したくて、タブレットのMiix 2 8にインストールしました。

こちらへアクセスして32ビットバージョンを選択し、ダウンロードに90分、インストールに2時間くらいかかりましたが、先ほど無事にインストールされました。

Lumia 535を使ってPhoneのほうはTechnical Previewを使っていたので「やっぱ似てるなー」というのが第一印象です。

試しながら気づいたことを書いていきます。

  • 回転ロックがかかっていて、横画面にならなかった

    →アクションセンターを開いて「回転ロック」を解除した。
     
  • ログイン直後に開いていたスタートメニューの閉じ方が分からない

    →いったんタブレットモードをON→OFFしたら画面上のスタートボタンでメニューが閉じるようになった(謎)
     
  • OneDriveを更新しろと言われ、更新しようとしたところ「先にアンインストールしろ」と言われた

    →OneDriveは8.1の時についてきたものでアンインストールできない。通知領域のOneDriveアイコンをタップしたら同期するフォルダの選択画面がでて選択したら動いている様子(謎)
     
  • Office 365でWordやExcelなど一式をインストールしてあるけど、スタートメニューに「新しいOfficeを始めよう」という不思議なアイコンが出てきた

    →タップしたら「Officeは最新です。」という表示とSwayの紹介リンクが表示された。
      新しいOfficeって、Swayのこと・・・???
     
  • そういえばスタートメニューが縦にも並ぶようになって配置が少しおかしくなった

    →調整しなきゃ
     
  • Lenovoの「Companion」を起動したら更新を促された

    →素直にダウンロード&インストール
     

2015年7月16日木曜日

[解決] やっと繋がった!MediaPad T1 7.0がWi-FiのSSIDすら見つけてくれない時の対処方法

母親に貸そうと思っていたAndroidタブレットが使い物にならなくなったので、つい新しいのを買ってしまいました。

ちょうどガジェット系のニュースに「1万円タブレット」として出ていた「MediaPad T1 7.0」です。


職場で休み時間にセットアップしたときはZenFone 5のWi-Fiテザリングを使ったのですが、自宅でWi-Fi接続しようとして困りました。

SSIDが見つからないのです。
ご近所のと思われるSSIDはいくつも見つかるのに。

そういえば以前から、Windows 8.1タブレットのMiix 2 8も繋がらなくなっていました。

さほど使っていなかったのと、ネット閲覧程度ならZenFone 5のBluetoothテザリングで十分だったのでそのうち調べようと後回しになっていたのです。

と言うわけでググったら速攻で見つかりました

無線LAN親機に設定したチャンネルが問題だったのです。


このように「13チャンネル」(ch 13)をメインで使っていたのですが、Miix 2 8はこのチャンネルが見えないそうです。(倍速モードのチャンネルは9)



「11チャンネル」を使うようにしたら、Miix 2 8もMediaPad T1 7.0もちゃんとSSIDを見つけてくれて無事に接続できました。

◇◇◇

ちなみにこの設定は元々「自動」でしたが、大きなファイルをダウンロードするなど負荷が上がるとパソコンやスマホの通信が切れてしまうという障害がありました。

混雑しているからと自動でチャンネルを変更してしまうために起こる現象なので固定にしたのです。その際にご近所と被りづらそうな大きめの数字に設定したのが敗因でした。

分かってしまえば簡単ですが、結構長いことハマりました。

2015年7月10日金曜日

[解決] iOSの実行時バージョン判定による分岐は3種類

昨日のエントリでも使いましたがiOSのアプリ開発ではバージョンを判定して処理を分けることが時々あります。

で、ググると3種類出てきてどれを使おうか軽く悩みました。

SDKに登場した順に並べるとUIDeviceを使うのが一番古く、NSProcessInfoはiOS8で初登場だそうです。

  • [UIDevice currentDevice].systemVersion
  • NSFoundationVersionNumber
  • NSProcessInfo
現在開発中の業務アプリはiOS7向けなのでNSProcessInfo以外を使うこととなり、中でも新しいNSFoundationVersionNumberを使うようにしました。

実装はここを参考にしました。







昨日のエントリも更新しておこうっと。

2015年7月9日木曜日

[解決] Xcode 6.4のMaster-DetailテンプレートがiOS 7.1のiPadで動作しない不具合の回避

こちらと同じ現象で困り、別の方法で回避しました。

Xcode6でiOS7向けにMaster-Detailアプリを作る時の注意点 - 技術はメシのタネ








要は、iOS8にしか存在しないdisplayModeButtonItemにアクセスしているから悪いのです。

と言うわけで2箇所(AppDelegate.mとMasterViewController.m)ある当該部分をiOS8以前は実行しないよう、このようにif文で囲みました。

if (NSFoundationVersionNumber > NSFoundationVersionNumber_iOS_7_1) {
    navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
}

これで良いのか悪いのかがサッパリ分からないのでスッキリしません。
たとえばこのような疑問が残ります。
  • バージョンで見るのが正しいのか?フィールド(Selector?)の有無で判断するほうが良いのではないか。
  • 比較に使っている定義がiOS7.1までしかない。コードとしては「7.1より大きいとき」となるので万が一7.2などが出たら動かない(出ないだろうけど)
  • iOS7以下の時に何か実行しなければならない処理はないのか。
気にはなりますが、回避はできたのでひとまず良しとします。

なお比較の方法はここを参考にしました。









2015年7月6日月曜日

[解決]古いAndroidタブレットを初期化したら使い物にならなくなった(追記あり)

(追記20151107)解決した方法はこちら




ガラケーを使っている母親がスマホに興味を持ったようです。

音声で検索とかできるのなら私にも使えるかな、と。

もともとメールは出来るので、まぁいけるかなと試しにタブレットを貸すことにしました。

ちょうど手元にLGのL-06CというAndroid 3.1なタブレットが眠っていたのでそれを渡そうと思い、リセット(初期化)したのが運の尽きでした。

二度とアプリをインストールできなくなってしまったのです。

一番最初にやった初期化はこのようにしました。
  1. 初期化する
  2. Wi-Fiを使えるようにする
  3. 母親用のGoogleアカウントを作成する
  4. 「マーケット」アプリを起動する
  5. 「マイアプリ」へ移動する →何も表示されない
  6. 再起動する
  7. 再び「マーケット」アプリの「マイアプリ」へ移動する →今度は表示された
  8. プリインストールされているアプリを更新しようとする
ここで「サーバーからの情報の取得中にエラーが発生しました。[RPC:S-7:AEC-7]」などと表示されてインストールに失敗します。

エラーメッセージでググって関連ありそうな作業(ストアアプリのデータ消去など)をしてみたり、何度も初期化しながら作業の順番を変えてみたり、使うアカウントを私の普段使いのにしてみたり、とにかく色々試したのですが、今のところ全滅です。

時々エラーメッセージ中の数字が変わるくらいです。

やはりAndroid 3.1はGoogleに見捨てられた子だということなのでしょうか・・・。

(追記20150708)

b-mobileのSIMがあったので挿してリセットからやり直してみましたが結果は変わらず。



2015年7月4日土曜日

スマホから古いプリンタに写真を印刷したくて「Google クラウドプリント」を試したけどイマイチだった

時々妻から「園のお友達にあげるから印刷して」とLINEで写真が送られてきます。
それを印刷するのがとても面倒でした。

というのも我が家のプリンタはキヤノンの「MP980」という割と古めの機種だからです。かろうじてWi-Fiには対応していますがスマホからは印刷できません。というわけで

  1. 写真を自宅パソコンで開けるようにするため、Dropboxなどに入れておく
  2. 普段行かない2階自室へ行ってプリンタの電源を入れる
  3. プリンタの給紙トレイを開けてL版の用紙を設定する
  4. 1階リビングのパソコンで写真を開く
  5. 必要なら写真を加工(主にトリミング)する
  6. パソコンから印刷する
  7. 2階のプリンタへ写真を取りに行く。
  8. 写真や用紙に問題がないか確認する
  9. プリンタの電源を切り、L版の用紙を片付けて、給紙トレイを閉じる
という手順が必要になります。

ここはやはりスマホから、できれば妻自身が直接プリンタで印刷できるようにすべきだろうと思い、ちょっと調べて「Google クラウドプリント」にたどり着きました。

対応プリンタなら設定するだけで、非対応プリンタならChromeを入れたパソコンをプリンタサーバ的に使うことで、スマホから印刷できるようになるとのことです。

なお、予想通りMP980は非対応でした。

Chromeは入っているのでプリンタを設定して使ってみました。

まずはPCからこちらのページへアクセスして「今すぐ試してみる」を押してプリンタを選択してみます。A4用紙にそれっぽいページが印刷されたので、うまくいったと思います。

次にスマホから写真を選んでメニューを開き「印刷」してみました。当然用紙は「L版」を選択したのですが・・・手差しのL版ではなく、トレイのA4用紙で印刷されました。

ここから軽く2時間くらい試行錯誤を繰り返しました。

プリンタの設定でL版を優先しそうな設定にしても、A4でしか印刷されません。

そのままプリンタの設定の深いところにある「アプリケーションソフトの給紙設定を無効にする」を設定した場合、逆にL版でしか印刷されません。


お手上げ状態です。

クラウドプリントを使おうとすると常時パソコンも起動しておかないといけないですし、いっそ素直にプリンタを新しくしようと思います。

キヤノンなら「スマフォトプリント」というのが使えそうですし、Android4.4(KitKat)のスマホならキヤノンの専用アプリを使わなくても印刷できそうです。

でもインクは予備が結構残っているので、年末までに使い切って新しいモデルが出たら入れ替えようかなと思います。

あとエプソンなど他メーカーのも少しは見ておこうかな。


2015年7月2日木曜日

Xcodeはblockのところも補完してくれる


できました。ありがとうございますm(_._)m

ちょうどAssetsLibraryのところでblockだらけだったので、同じく手打ちしてた...。

TimeMachineのハードディスクを入れ替えようとして少し戸惑った

TimeMachineのハードディスクを借り物から自分のに入れ替えました。

公式のドキュメントに従ってやったのですが、そもそもMacのことがよく分かってないので戸惑いました。

Time Machine:2 台のバックアップドライブ間でバックアップデータを転送する方法


新しいバックアップドライブのフォーマットを確認する

  • ディスクを初期化しないといけないかと思ったけど、パーティションを選んでフォーマットを選び直すだけで良かった
  • Mac OS拡張のフォーマットだけで4種類もあって一瞬悩んだ。特に大文字/小文字を区別の有無は良く分からなかったけど、区別ありのほうにした
新しいバックアップドライブにアクセス権を設定する
  • 「このボリューム上の所有権を無視」チェックボックスにチェックが入っていたので、どうしようかと思った。鍵アイコンをクリックして変更できた。
転送元のドライブから新しいドライブにバックアップデータをコピーする
  • 最後の「1分」になってから1時間くらいかかった。これはもうダメかと思った。
新しいドライブを使うように Time Machine を設定する
  • 新しいドライブを選んだ後、Time Machineの画面で「最古のバックアップ」「最新のバックアップ」が「なし」と表示され、「次回のバックアップ」が120秒くらいのカウントダウンとなった。
最後のは「あ、TimeMachineのディスクと認識されてないかも」と思ってかなり焦りました。

結局バックアップが始まったら100MB程度が処理されたのでフルバックアップじゃないとわかり、ひと安心しました。

Macってホント不親切だと思うんだけど気のせいだろうか。

UINavigationControllerにTab Barを置くときの注意事項

今作っている業務アプリは、iPadの横向き専用です。

UISplitViewControllerのMaster/Detail両方ともTop BarとBottom Barを表示しています。

UIにチカラは入っていないので、できるだけそれぞれのBarにボタンを足してアレコレしてしまっているのです。

ところで今回画像のサムネイル表示をしようとして、Detail側にTab Barを追加しようとしました。

何パターンか試したのですが

・DetailとしてUINavigationControllerではなくUITabBarControllerを繋げる

  →× NavigationのBottom Barが消える(当たり前)

・DetailのUINavigationControllerからUITabBarControllerを繋げる

  →× Tab Barの上にNavigationのBottom Barが重なって動かせない

・DetailのUINavigationControllerから繋がったUIViewControllerにUITabBarを配置する

  →○ とりあえず希望の動作になった。
      場所はConstraintsで対応。両端に伸ばすため左右のSpaceがSuperViewの「-16」なのは少し不思議。

とりあえず。

動画の再生時間をALAssetPropertyDurationで取得したけど値の意味がわからない

AssetsLibraryを使って、カメラロール画像を選択する画面を作っています。

なにしろ標準のUIImagePickerControllerを使うとサムネイルしか表示されず、撮影日時すら分からないので。

画像の情報は無事に取得できているんだけど、動画の再生時間の値の意味がちょっと分からない。

NSNumber *duration = [asset valueForProperty:ALAssetPropertyDuration];
NSLog(@"Duration %@", duration);


すると、2秒の動画で1.558333...、9秒の動画で8.718333....みたいな数字が取れる。

これはどう処理するものなのだろうか。
リファレンスを見ても「NSNumberでラップされた再生時間(play time)」としか書いてないし・・・。

UITextViewのフォントが変わらない(更新あり)

StoryBoardでTableViewCellにUITextViewを置いた。

実行すると文字が小さい(不慣れな方が使うので大きくないとダメ)ため、StoryBoardでUITextViewのFontプロパティを変更するも、変わらなかった。

元々はHelvetica neue 17.0となっていたのを34.0にしても変わらず。

TextをPlainからAttributedにしてもダメ。

これ昔もハマった気がするんだけどどうやって解決したんだっけ。

(追記)
とりあえずコードで変更されることは確認できたけど、こんなんじゃない。

UITextView *tv = (UITextView *)[cell viewWithTag:2];
tv.font = [UIFont systemFontOfSize:34.0];