就職活動をしようと思います。

1年間ほど、個人事業主として活動をしてきましたが、就職活動をしようと思います。

会社を辞めて、個人事業主として活動を行なってきましたが、あまり向いてないのかなと思うようになりました。
ネガティブなキーワードが並ぶ事になるのはあんまり好きではないのですが、この1年の反省点などを上げていこうと思います。

  • 営業活動をほとんどしなかった
  • 技術革新のスピードについていけない
  • 利益が出せていない(深刻)
  • 引きこもってるので運動不足
  • 夜型になって、若干、精神を病んでいってる
  • 世界観が縮小していく感じがある

受注案件は、ほぼtwitterの知り合いの方からと、サイトからのお問い合わせを頂いたりなどでした。もちろん、全てを受注したわけでもないので、見積りのみ行なって消える案件のほうが多いです。見積りは時間がかかるので、受注できない場合の損失たるや、かなりしんどいわけです(精神的にも金銭的にも時間的にも)。案件の見積りが同時期に重なると、さらに気を遣います。一人なので、どんどん不安になっていきました。

本来は、受託開発はできればしたくなかったので、AndroidアプリやWebサイトを作ってそちらで利益を生める仕組みを作って、そちら側で生活基盤を作っていこうというつもりだったのですが、思ったよりもうまくいきませんでした。アプリの世界では、アイデア勝負とスピード勝負なのですが、どちらも自分がそこまで得意ではないというのがありました。お題があれば、割とガーッとできるほうなのですが、アイデアを出すのが人並みといいますか…。
ただ、人が困ってるのをtwitterやFaceBookで見かけたときにアプリを作るのは好きでした。それで生み出されたのが、計画停電情報であり、WiFiCutterであり、先日リリースしたWordPressReferenceだったりします。

技術革新のスピードについていけてない、というのは、主にAndroid, HTML5などですが、追えていません。まぁ全部を追う必要もないですし、必要なものだけ追えればいいと思うのですが。追えてないと不安という辺り、自分は技術者なんかなと思います(経営者ではなく…)。

利益が出せていないというのが一番深刻です。もうめっちゃ不安になります。案件終わってポンとお金が入ったときにはテンションあがります!しかし、当然ながら生活してれば減っていきますから、それをカバーできるほどの仕事をしたいのですけど、だいたい見積りしてダメの繰り返しでした。うーん。高くないと思うんだけれども…。一応、アプリの広告やWebサイトの広告やアフィリエイトの収入があるので、全くのゼロというわけではありませんが、預金は貯まるどころか減る一方でした。

引きこもってるので運動不足なのです。夜な夜な、ウォーキングにいったりする日々です。
よく、twitterで瀬戸内クラスタに「自転車で通勤しましょ♪」っていうサイトしているのに自転車通勤してないじゃん!と突っ込まれ(だって自宅が職場だし!)、枕を涙で濡らしたものです。
運動しようと思ってランニング始めたら膝を痛めたのもいい思い出(T_T)

夜型の生活は、やはりよろしくありません。以前に3交代制の仕事をしていたので、夜型は心を病むのはよく知っていたのですが、個人でやるようになると、なぜか夜のほうがプログラミングがはかどるので、どんどん夜型になってしまいました。こうなるとダメですね…。朝方に戻しても、すぐ夜型になってしまいました。

人になかなか会わないので、世界観が縮小していく感じがあります。本を読む時間も減りました。なんか、本を読む時間があるんならアプリ作って収益化しないといけないという強迫観念みたいなものに取り憑かれてる感じがあります。そうなると、どんどん世界観が縮まっていきます。個人で活動する前に蓄積していた読書量のバックボーンのおかげで、なんとかなってる感じです。
勉強会などのイベントは好きなので参加したり、講師をやったりと、いい経験はしていますが、やはりリアルに人に毎日会って仕事をするというのは精神上大事なことなんだなと感じました。

個人事業主は、よくも悪くも自由ということなんですが、なんせ辛いのは、孤独感です。まだお金は多少あるので、続けることもできるのですが、やっぱりチームで仕事がしたいという欲求が強くなってきています。自分で儲けて、人を雇えるようになって、というのが理想かもしれませんが、たぶんそれは今の自分の力では難しいだろうなと思いました。完全に力不足です。

また、SE・プログラマーをやっていて、自分は転職をしたことがありません。別に、前の職場が嫌いというわけではありません。デスマったことありますが、いい思い出もたくさんあります。しかしながら、技術向上や職場改善にアクティブな同僚は正直、いませんでした。
しかし、twitterや勉強会で知り合ったような、アクティブで好奇心の強いエンジニアの方たちと仕事ができたら凄い楽しいんじゃないかと思っています。そういうところで自分の力を発揮したほうが世の中に貢献できるだろうし、自分の実力も高める事ができるんじゃないかなと思っています。

ということで、もう一度、どこかの会社に属してみようという気になりました。
自分からも探してみたいと思っていますが、是非うちにきてみないか?という企業様がいらっしゃいましたら、是非是非ご連絡ください。

※予想以上にお声をかけて頂きました。誠にありがとうございました。面接を受けたい企業様を数社に絞りましたので、大変申し訳ありませんが、ここで終了とさせていただきます。

twitter: @patorash
メールアドレス: oko@okolabo.com

性別:男
年齢:31歳

できること、やってきた事など:

  • html
  • css
  • JavaScript(jQueryもOK)
  • PHP(経験:5年くらい)
  • Android(Java)(経験:1年9ヶ月。ACE取得済み。共著でAndroidタブレットアプリ開発ガイド
    という本出してます)
  • WordPress(ブランク有り。仕事でプラグイン作ったりしてました)
  • CakePHP1.1〜1.3(ブランク有り。Ktai Library使ってケータイサイト作ったりしてました)
  • Chrome Extension作成
  • GAE for Java(slim3で少々)
  • GAE for Python(Djangoで少々)

Androidアプリ
Android Market(patorash)

Chrome Extension
Chrome WebStore(patorash)


Chrome Extension:WordPressReferenceをリリース

Chrome Extension作りにハマっているパトラッシュです。
先日、WordPressReferenceJPというChrome Extensionを作りました。

どんなエクステンションかというと、WordPressの関数やテンプレートタグなどを調べるのに便利なエクステンションです。
調べたい関数名を入力すると、WordPress Codex日本語版の該当ページを表示してくれます。このタグどんな引数を取るんだったっけなー?とか思った時に超便利でしょう。

Chrome Extension: WordPressReferenceJP

しかし、上記のは、日本語版なのです。参照サイトも。しかも、参照サイトが日本語版なせいか、日本語化されてなくて説明がない関数があったり、全ての関数を網羅していません(俺がHTMLの解析が難しくてやめたからとも言えるけど…)。まぁ、英語は苦手だから日本語のがいい!という方は上のを使ってください。

で、下のが英語版です。WordPressReferenceというChrome Extensionです。
twitterで、日本語版は抜けがあるし、最新版の情報を知りたいから英語版のを作ってくれたら嬉しいです!と言われたので、作りました。英語版のほうは、サイトの解析がものすごくし易かったので、たぶん全部の関数を網羅していると思います。
もちろん、本家なので、関数の情報がないわけがない!
英語でもいいから使いたいという人はこちらをどうぞー。

Chrome Extension: WordPressReference

WordPress界隈の方々にたくさん使っていただけると嬉しいなぁ〜(^_^)


Chrome Extension:楽天Books検索をリリースしました。

先日、四国GTUG主催のHTML5ハッカソンに参加してきました。
私はHTML5のことをよくわかってないので、全然HTML5の事を活かしたアプリとか考えれなかったので、前回の中国GTUGのHTML5ハッカソンの時と同様に、Chrome Extensionを作ることにしたのでした。アイデア出し自体も全然やってなかったけど、なんかアイデア降ってくるだろうと思っていたのですが、全然降ってこなくて、悩んだ挙げ句に考えたのが、楽天WebAPIを使ったアプリです。そう、困ったときの楽天WebAPI。

選択した文字列を右クリックでAndroidに送れるChromeToPhoneがあるので、右クリックにメニューを加えることができるだろうと。それだったら、選択した文字列から楽天Books検索できたらいちいち楽天のページ開かなくてもいいんじゃねーの?と思って、それを調べていました。HTML5ハッカソンでは時間が全然足りなくて半分もできなかったのですが、今日完成させることができたので、公開しました。よかったらどうぞ!

Chrome Extension 楽天Books検索

以降、作ったときのポイントなどを記述していきます。

一番困るかなと思っていたのは、クロスドメインでのAjaxなのですが、XMLHttpRequestクラスとパーミッションに指定ドメインを追加をすれば、簡単に通信できました。しかし、その前に実はjQueryを使ってgetJsonメソッドで取得できていました。jsonpで取れたので、これだとパーミッション要らない…。でもお行儀が悪く見えるかなーと思ったので、XMLHttpRequestを使うように修正しました。

前に作成したChrome ExtensionのTitleListでは、BrowserActionのpopup.htmlでのコーディングしかしてなかったのですが、今回はContextMenusを使うをつかったので、background_pageのbackground.htmlでのコーディングをしてます。ちょこっと。

// background.htmlで読み込んでいるbackground.js
** 検索文字列を設定*/
var searchWord;

function rakutenBooksSearch(info, tab) {
	searchWord = info.selectionText;
	// 検索キーワードを変数に持たせて、タブを表示する。
	// タブ側のjavscriptで表示処理を行なう
	chrome.tabs.create({
		"url" : "popup.html",
		"selected" : true
	});
}

var context = "selection";
var title = "楽天Books検索";
var id = chrome.contextMenus.create({
	"title" : title,
	"contexts" : [context],
	"onclick" : rakutenBooksSearch
});

まず、後半のほうで、右クリックにメニューを追加するためにchrome.contextMenus.createメソッドを呼んでいます。contextsにselectionを渡して、文字列を選択したときだけ表示されるようにしています。選択したら、rakutenBooksSearch関数が呼ばれるわけです。
そして最初で、background.html側で定義しているjsの変数にアクセスするので、searchWordという変数を作成してます。rakutenBooksSearch関数内で、選択文字列を取得し(info.selectionText)、それをsearchWordに保存しています。保存後、検索結果をタブで表示するために、タブの生成をしています。

popup.htmlという名前を付けていますが、別にpopup.htmlじゃなくてもいいです。
タブで表示した側のhtml(popup.html)で、JavaScriptを読み込みます。
そのJavaScriptで、background.htmlで使っているJavaScriptの変数を読み取ります。
chrome.extension.getBacgroujndPage();がそれです。

// popup.htmlで読み込んでいるJavaScript
// background.htmlの要素を取得する
var bg = chrome.extension.getBackgroundPage();
// 現在の楽天検索の対象ページ
var currentPage = 1;
// 楽天検索結果の最大ページ(1で初期化)
var pageCount = 1

// 右クリックから送られてきた文字列で楽天Books検索を実行
rakutenBooksSearch(bg.searchWord, currentPage);

function rakutenBooksSearch(word, page) {
    // 省略。urlやクエリの組み立てなどを行なう

    // manifest.jsonのpermissionsに定義したドメインのurlに対するXMLHttpRequest
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url + queryString, true);
    // 通信状態が変わったら呼ばれるコールバックを定義
    xhr.onreadystatechange = function() {
        if(xhr.readyState == 4 && xhr.status == 200) {
            // 通信は成功
        } else if (xhr.readyState == 4) {
            // 通信は終了したがエラー
        } else {
            // 通信が終了していない
        }
    }
    // 通信開始
    xhr.send();
}
// 以降、省略

xhr.onreadystatechangeにコールバックを定義するのですが、そこで処理を適当に定義するわけです。

最後に、manifest.jsonを公開しておきますので参考にどうぞ。
パーミッションに、Chrome固有の機能だけでなく、ドメインを指定しておくのがミソです。

{
	"name": "楽天Books検索",
	"version": "1.0",
	"description": "楽天Booksより総合検索を行ないます",
	"background_page": "background.html",
	"permissions": ["tabs", "contextMenus", "http://api.rakuten.co.jp/"],
	"offline_enabled": false,
	"icons": {
		"16": "icon_16.png",
		"48": "icon_48.png",
		"128": "icon_128.png"
	}
}

以上です。

ブラウザを使った業務アプリなどだったら、決まった操作とかばかりならChrome Extensionを作る事でものすごい効率がアップするんじゃないかと思います。
Chromeというブラウザ上のみではあるけれど、可能性が拡がってすごいですね、本当に。html5にも詳しくなって、便利なExtensionが作れたらなぁ〜と思います。


Android:アプリ内課金を実装してみた(キャンセル編)

前回、前々回とアプリ内課金の購入編リストア編を取り上げてきました。

次はキャンセルの場合の処理です。WiFiCutterでは、購入したら別にキャンセルはできないのでいらんかなーと思っていたんですが、どうもクレジットカードがなんらかの原因で使えない場合はキャンセルが発生することがわかりました。
それが発生して教えてくれたのがtwitterで仲のよいフォロワーさんだったので助かりました。

やっぱりキャンセル処理も必要だなぁということで、実装してみました。例によって、テクブ本ベースです。まずは、静的なアプリ内課金テストを行ないました。Activityから注文処理を呼び出します。そのときに渡すアプリ内サービスIDを、テスト用のandroid.test.canceledにしておきます。

// paidはPreferencesオブジェクト
paid.setOnPreferenceClickListener(new OnPreferenceClickListener() {

    public boolean onPreferenceClick(Preference preference) {

    // キャンセルのテスト
    if (!mBillingService.requestPurchase(TEST_PURCHASE.CANCELED)) {
        Toast.makeText(WifiCutterPreferences.this,
            R.string.no_connected,
            Toast.LENGTH_LONG).show();
    }
    return true;
}

これを実装すると、偽物の購入画面が現われます。偽物のカードを選択して、購入を押すと、擬似的に購入処理が走りますが、キャンセルの疑似処理なので、キャンセルが発生します。

購入を押すと…

上の画像のように、注文が失敗します。これでキャンセルのブロードキャストが発生します。
PURCHASE_STATE_CHANGEDでJSONが送られてくるのですが、ordersのJSONArrayの中の注文処理のpurchaseStateが1になっています。purchaseStateは、0が購入、1がキャンセル、2が購入の払い戻しのようです(2は試してないけど)

{
    "nonce":******************************,
    "orders":[
        {
            "notificationId":"android.test.canceled",
            "orderId":"transactionId.android.test.canceled",
            "packageName":"com.okolabo.android.wificutter",
            "productId":"android.test.canceled",
            "purchaseTime":1321622270724,
            "purchaseState":1 // キャンセル
        }
    ]
}

BillingService.javaの、purchaseStateChangedメソッドで、購入もリストアもキャンセルもやることになります。テクブ本のサンプルだと、vp.purchaseStateの値をチェックしていないので、なんでも購入処理になってしまうので、vp.purchaseStateを比較して処理を分けます。

また、テクブ本ではメソッドの最後にPurchaseController.purchaseComplete();を実行していましたが、購入時とキャンセル時で表示されるトーストを変えるために、最後ではなくorder毎の処理の際にメッセージを出すようにしました(まぁ自分のアプリの場合は1注文しかないからという算段ですが。あまりよくないかも)。それに合わせて、PurchaseStateを引数に持たせるようにコールバックメソッドを修正しています(別に見習わなくてもいい部分です)

あ、PurchaseStateはテクブ本のConsts.java内に定義されているenumです。

// BillingService.java
    private void purchaseStateChanged(String signedData, String signature) {
        ArrayList<Security.VerifiedPurchase> purchases;

        // 受信したトランザクション情報の整合性を検証する
        purchases = Security.verifyPurchase(signedData, signature);
        if (purchases == null) {
            return;
        }

        // トランザクション情報を処理する
        ArrayList<String> notifyList = new ArrayList<String>();
        for (VerifiedPurchase vp : purchases) {
            if (vp.notificationId != null) {
                // トランザクションごとに通知IDを保持する
                notifyList.add(vp.notificationId);
            }
            // 制限を外す
            if (vp.productId.equals("android.test.canceled")) {
                if (vp.purchaseState == PurchaseState.PURCHASED) {
                    Log.d(TAG, "purchaseState == purchase");
                    purchase();
                } else if (vp.purchaseState == PurchaseState.CANCELED) {
                    Log.d(TAG, "purchaseState == cancel");
                    purchaseCancel();
                } else if (vp.purchaseState == PurchaseState.REFUNDED) {
                    Log.d(TAG, "purchaseState == refunded");
                    purchaseCancel();
                }
                // 画面の更新とトーストによる通知を行なう
                PurchaseController.purchaseComplete(vp.purchaseState);
            }
        }

        // トランザクション情報を取得できた場合は、CONFIRM_NOTIFICATIONSリクエストを送信する
        if (!notifyList.isEmpty()) {
            String[] notifyIds = notifyList.toArray(new String[notifyList.size()]);
            confirmNotifications(notifyIds);
        }
    }

purchaseStateの値を比較して処理を変えています。purchaseメソッドとpurchaseCancelメソッドはBillingService.java内に定義した購入処理とキャンセル処理です。これで、購入、キャンセル、リストアとある程度はできるようになるんじゃないかと思います。

一通り動作を確認したら、Activityの購入処理で渡していたアプリ内サービスIDをandroid.test.canceledから正規のIDに変更しておきましょう。これで、あとはマーケットに載せてテストアカウントでテストしてみて、大丈夫だったらOKです。

アプリ内課金のテスト方法についても説明してありますので、よかったらテクブ本を手に取ってみてください!(^O^)


Android:アプリ内課金を実装してみた(リストア編)

前回、Androidのアプリ内課金の購入編を書きました。
今回は、リストア編です。

アプリ内課金の課金アイテムは2タイプあります。

  • Googleアカウントに紐づいて1度だけ購入するタイプ
  • 何度でも購入できるタイプ

WiFiCutterで実装したのは、1度だけ購入するタイプです。このタイプの場合、1度買っていると、また買おうとすると「もう購入済みだよ!」と怒られてしまいます。WiFiCutterの初回リリース時点では、全然気にしてなかったのですが、twitterのフォロワーさんから、他の端末に入れたら買えないって言われたよと指摘を受けたので、リストアに挑戦することにしました。

で、リストアってどのタイミングで、どんな風に処理するのかが全然わからない。テクブ本にも載ってない…。Googleのサンプルを見たら、アプリの初回起動時にリストア処理をしていたので、初回起動かどうかのフラグを持っておいて、そのときのみ行なうのがよさそうです。
今回はテクブ本のソースに追加する形で実装したので、ソースを載せておきます。

リストアの場合は、Android Marketアプリに対して、RESTORE_TRANSACTIONSのリクエストを送ります。これは、ユーザの今までのアプリ内の購入履歴を問い合わせるためのリクエストです。今までの購入履歴を受け取り、それを元に、復元したりするわけですね。
RESTORE_TRANSACTIONSの場合は、ノンスが必要なので、ノンスを設定します。

BillingService.java内で実装したRestoreTransactionsクラス

/***
 * RESTORE_TRANSACTIONSリクエストを送信する
 */
class RestoreTransactions extends BillingRequest {
    long mNonce;

    @Override
    protected long run() throws RemoteException {
        // ノンスを生成する
        mNonce = Security.generateNonce();
        // 基本Bundleを作成する
        Bundle request = makeRequestBundle("RESTORE_TRANSACTIONS");
        request.putLong(Consts.BILLING_REQUEST_NONCE, mNonce);
        // MarketServiceに送信する
        Bundle response = mService.sendBillingRequest(request);
        // リクエストIDを返す
        return response.getLong(Consts.BILLING_RESPONSE_REQUEST_ID,
                Consts.BILLING_RESPONSE_INVALID_REQUEST_ID);
    }

    @Override
    protected void onRemoteException(RemoteException e) {
        super.onRemoteException(e);

        // 例外が発生したらノンスを削除
        Security.removeNonce(mNonce);
    }
}

// RESTORE_TRANSACTIONSリクエストを処理する
public boolean restoreTransactions() {
    return new RestoreTransactions().runRequest();
}

これを、Activity側のonCreateなど初回起動時に呼び出すわけですね。
適当なActivityのonCreateで初回だけ呼び出します。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        // 初回起動か確認
        boolean isFirst = preferences.getBoolean("is_first", true);
        if (isFirst) {
            Editor editor = preferences.edit();
            editor.putBoolean("is_first", false);
            editor.commit();
            // まず、既にアプリを購入済みか確認する
            mBillingService = new BillingService();
            mBillingService.setContext(this);
            mBillingService.restoreTransactions();
        }
    }

これを呼び出したら、マーケットからPURCHASE_STATE_CHANGEDで購入履歴が送られてきます。形式は、購入時のJSONとほぼ同じですが、RESTORE_TRANSACTIONSの場合、notificationIdがありません。
notificationIdは、「購入によるアプリ側の処理がちゃんと行なわれた」ということをマーケットに通知するためのものなのですが(CONFIRM_NOTIFICATIONSで)、RESTORE_TRANSACTIONでは既に一度購入処理を行なっているので、マーケットに処理がうまくいった事を通知する必要がないので、ありません。

追記:実際に送られてきたJSONを書いときます。
(一部塗りつぶしてますが)

{
    "nonce":**************************,
    "orders":[
        {
            "orderId":"********************",
            "packageName":"com.okolabo.android.wificutter",
            "productId":"hoge",
            "purchaseTime":1321026274058,
            "purchaseState":0
        }
    ]
}

ordersがJSONArrayになっているので複数の購入履歴が一度に取れます。

PURCHASE_STATE_CHANGEDが送られた場合の処理は、既に実装済み(のはず)なので、あとは自動的に購入時の処理が行なわれます。何度でも購入できるアイテムなどの場合は、orderIdを比較するなりして、アイテムが増えないようにするべきでしょう。

テクブ本はタブレットアプリ開発ガイドと銘打っていますが、アプリ内課金などスマートフォン向けの実装も説明してあるので、ためになると思います(宣伝)!!