健康意識の高まり

いろんなメモなど

レシーバが nil だった場合の戻り値

ハマったのでメモ。
詳解 Objective-C 2.0 第3版 の63ページより。

返り値の型 返り値
オブジェクト nil
ポインタ NULL
整数 0
それ以外 OS のバージョンなどで異なるため、不定と考えるのが無難

背景

UILocalNotification に設定した通知を削除するために、設定時に userInfo に適当な値を設定して、
キーをもとに通知を消す以下の様なコードを書いた。

- (void) setNotification {
    UILocalNotification *notification = [[UILocalNotification alloc] init];
    
    notification.fireDate = [[NSDate date] addTimeInterval:60]; 
    notification.timeZone = [NSTimeZone defaultTimeZone];
    notification.alertBody = @"Notification";
    notification.alertAction = @"Open";
    notification.soundName = UILocalNotificationDefaultSoundName;
    notification.applicationIconBadgeNumber = 1;
    [ notification.userInfo setValue:@"set" forKey:@"notificationTime" ];

    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

- (void) deleteNotification {

    for(UILocalNotification *notification in [[UIApplication sharedApplication] scheduledLocalNotifications]) {

        if(  [notification.userInfo objectForKey:@"notificationTime"]  ) {
            [[UIApplication sharedApplication] cancelLocalNotification:notification];
        }
    }

}

しかし、エラーも何も起こらず、 if 内に分岐しない。
デバッグしてみると、そもそも notification.userInfo プロパティは NSDictionary 型で、何らかのNSDictionary 型のインスタンスを生成して値として設定しない限り nil であることがわかった。

同時に、そういえばレシーバが nil のときの挙動について何かで見たぞと思いだし、詳解 objective-c を調べてみると、やはりそうだった、というオチ。
なので、以下のように修正。

notification.userInfo = @{ @"notificationTime":@"set" };

Android でアニメーションを連続して順番に実行する方法

「1秒間でフェードイン、3秒くらいその場で停止し、3秒かけてフェードアウト」といった、特定の動きを連続して実行する仕組みが現在の Android にはないようです。

そこで、 setAnimationListener の onAnimationEnd メソッドを使って、アニメーション終了時に次のアニメーションを実行するようにします。

以下は、ある Fragment にアニメーションを付ける例です。

package com.example.testanimation;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;

public class MyFragment extends Fragment {
    public MyFragment() {}
    private TranslateAnimation animation = null;

    @Override
    public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       View view = inflater.inflate( R.layout.notice_fragment, container, false );
       return view;
    }

    private TranslateAnimation createAnimation () {
        TranslateAnimation fadeInAndOut = new TranslateAnimation(
                0.0f, 0.0f, -100.0f, 0.0f
        );
        
        fadeInAndOut.setDuration(1000);
        fadeInAndOut.setAnimationListener( new AnimationListener() {
            @Override public void onAnimationStart(Animation animation) {}
            @Override public void onAnimationRepeat(Animation animation) {}
            @Override
            public void onAnimationEnd(Animation animation) {
                View view = MyFragment.this.getView();
                
                TranslateAnimation stay = new TranslateAnimation(
                    0.0f, 0.0f, 0.0f, 0.0f
                );
                stay.setDuration( 3000 );
                stay.setAnimationListener( new AnimationListener() {
                    @Override public void onAnimationStart(Animation animation) {}
                    @Override public void onAnimationRepeat(Animation animation) {}
                    @Override public void onAnimationEnd(Animation animation) {
                        View view = MyFragment.this.getView();
                        
                        // third animation
                        TranslateAnimation fadeOut = new TranslateAnimation(
                            0.0f, 0.0f, 0.0f, -100.0f
                        );
                        fadeOut.setDuration( 1000 );
                        fadeOut.setFillAfter( true );
                        view.startAnimation( fadeOut );
                    }
                });
                view.startAnimation( stay );
            }
        });
        
        return fadeInAndOut;
    }

    public void notice () {
        Activity activity = this.getActivity();
        View view = this.getView();

        if ( this.animation == null ) {
            this.animation = this.createAnimation();

            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                    LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT
            );
            
            activity.addContentView(view, layoutParams );
        }
        
        view.startAnimation( this.animation );
    }
}

見ての通り、JavaScript とかでよくある入れ子地獄が発生しています。
これをキレイに書く方法ないんですかねー・・・。

Android でレイアウト xml に <fragment>タグなど用意せずに fragment を動的に表示させる方法

たとえばあるビューの上ににょきーんと fragment を表示させたいとき、など(この表現で伝わるのか謎ですが・・・)。

基本的な方針

Fragment のクラスに、以下の様な内容のインスタンスメソッドを追加し、アクティビティから適宜呼び出す:
アクティビティとビューを取得し、そのビューをアクティビティに追加する。

Fragment の例

notice メソッドを実行すると、アクティビティのビューの上からにゅいーんとアニメーションして出てくる Fragment の例です。

package com.example.testanimation;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.RelativeLayout;

public class MyFragment extends Fragment {
    public MyFragment() {}
    private TranslateAnimation animation = null;

    @Override
    public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       this.put( "onCreateView" );
       View view = inflater.inflate( R.layout.notice_fragment, container, false );
       return view;
    }

    private TranslateAnimation createAnimation () {
        TranslateAnimation animation = new TranslateAnimation(
                0.0f, 0.0f, -100.0f, 0.0f
        );
        
        animation.setDuration(1000);
        return animation;
    }

    public void notice () {
        Activity activity = this.getActivity();
        View view = this.getView();

        if ( this.animation == null ) {
            this.animation = this.createAnimation();

            RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
                    LayoutParams.MATCH_PARENT,
                    LayoutParams.WRAP_CONTENT
            );
            
            activity.addContentView(view, layoutParams );
        }
        
        view.startAnimation( this.animation );
    }
}

res/layout/notice_fragment.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_light"
    tools:context=".MainActivity" >

    <ImageView
        android:id="@+id/Icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/noticeMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/Icon"
        android:layout_alignTop="@+id/Icon"
        android:text="message1." />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/Icon"
        android:layout_alignBottom="@+id/Icon"
        android:text="message2." />

    <ImageView
        android:id="@+id/SmallIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/noticeMessage"
        android:layout_alignTop="@+id/noticeMessage"
        android:src="@drawable/ic_launcher" />
    
</RelativeLayout>

FragmentActivity の例

package com.example.testanimation;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class MainActivity extends FragmentActivity {
    
    private static final String NOTICE_FRAGMENT_TAG = "notice_fragment_tag";
    private MyFragment myFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        if ( savedInstanceState != null ) {
            this.myFragment = (MyFragment) this.getSupportFragmentManager().findFragmentByTag( MainActivity.NOTICE_FRAGMENT_TAG );
        }
        else {
            this.myFragment = new MyFragment();
            this.getSupportFragmentManager().beginTransaction().add( this.myFragment, MainActivity.NOTICE_FRAGMENT_TAG).commit();
        }
        setContentView(R.layout.activity_main);
        
        Button noticeButton = (Button) this.findViewById( R.id.startButton );
        noticeButton.setOnClickListener( new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // ボタンを押したらフラグメントの notice メソッドを呼び出すことで、にょいーんと出てくる。
                MainActivity.this.myFragment.notice();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

activity_main.xml :

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/startButton"
        android:layout_below="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start"
        />
</RelativeLayout>

※まだ Android 開発に慣れていないので、まずいコードがあるかもしれません。

comm の事業縮小から、無い頭で戦略とかについて思いを巡らせた

【特報】DeNA、「comm」事業を縮小へ
LINEの勢い止まらず、カカオトーク、DECOLINKも拡大路線を撤回
http://business.nikkeibp.co.jp/article/topics/20130625/250184/

競争地位戦略として、フォロワーはあり得ないんだろうなぁ。

記事によると comm の会員数は670万人。
ざっくり推定ですが、会員数のうち3割がDAUとすると200万人。
おなじくざっくり推定ですが会員数のうち5割がMAUとすると335万人。
たしかに LINE と比べると小さい数字かもしれないですが、それでもそんじょそこらのサービスに比べたらすごい数。

それでも、「リーダーにはなれそうにない。チャレンジャーにしても利用率がスマフォユーザーの数%(※)じゃ儲からない、そして社としてフォロワーにはならない」っていう話が社内にきっとあって、戦略あるいは作戦レベルの遂行が不可能だとわかると、戦術こねくり回すんじゃなくてちゃんと潔く縮小する。
なんかアホっぽいけど「ちゃんと考えてるんやなぁ」って思ってしまう。

「撤退条件は?」ってよくいうけど、そもそもまず「どういう市場で何を目指すのか」っていう具体的な戦略をはっきりさせておかないと、こういった大胆な縮小あるいは撤退の決断はできないなぁと改めて思いました。

記事に"今後、積極的な会員獲得を目的としたプロモーション活動はやめる方針"とあるので、半年以内に「クローズのお知らせ」のプレスリリースが出たりして。


※・・・スマートフォン市場規模の推移・予測(2013年3月)の「2014年度にスマートフォン契約数がフィーチャーフォン契約数を超える」節によると、
スマフォの契約数は2013年3月末には4,337万件に達する見込み、2014年3月末では5,915万件との予測なので、現在はまぁ5000万弱くらいと思われる。
comm の会員数は 670万なので13%くらい、予測DAU、MAUだとそれぞれ 4%、6.7%。
http://www.m2ri.jp/newsreleases/main.php?id=010120130328500

※2・・・社内の人から見たらてんで見当違いなこと言っている可能性は大いにあります

ActionBarSherlock の導入方法

基本的には Android 2.xでAction Barが使えるActionBarSherlockの使い方 - gabuchanの日記 の手順で完了。

もし、duplicate なんちゃらというエラーが出たら、sherlock とそれを使うプロジェクトの双方のプロジェクトで
右クリック→Android Tools→Add Support Library で更新作業をすれば出なくなった。

rails 3 での csrf 対策まとめ

rails3 だと、csrf トークンを投げずに post リクエストなどを送っても、

WARNING: Can’t verify CSRF token authenticity

が出ると同時にセッションがリセットされるが、POST リクエスト自体は通る。

csrf トークン自体は、form_for などのヘルパーを使ったら自動で埋め込まれる。
自分で form タグを書いた場合は

<%= hidden_field_tag :authenticity_token, form_authenticity_token %>

とテンプレートに書いて csrf トークンを埋め込む。

csrf トークンの検証に失敗した場合にエラーとしたい場合は、

class ApplicationController < ActionController
  
  def handle_unverified_request
    raise ActionController::InvalidAuthenticityToken
  end

end

と handle_unverified_request を上書きして、raise ActionController::InvalidAuthenticityToken などする。

NoMethodError: undefined method 'accept' for nil:NilClass が出た時の対処法

  • Gemfile に mysql2 を追加
gem 'mysql2'
  • activerecord-mysql2-adapter があればコメントアウト
# gem 'activerecord-mysql2-adapter'
  • bundle install
  • rails 再起動