20211224のAndroidに関する記事は3件です。

だからボクはオブジェクト指向が使いこなせない Android/Kotlin編

はじめに 本記事は、Android/Kotlin開発において、オブジェクト指向を使いこなしたい、という方に向けた記事です。 ※この記事は「だからボクはオブジェクト指向が使いこなせない Android/Java編」をAndroid/Kotlinで書き直したものです。 私が10年近くAndroidアプリ開発に関わってきて思うのが、開発現場でオブジェクト指向らしいコードを書けるプログラマーは、残念ながら全体の2割にも満たない、ということです。(あくまで私個人の感想です)。 逆に言えば、オブジェクト指向らしいコードを書けるようになるだけで、開発現場で重宝される人材になれます。Androidの最新ライブラリの使い方の勉強をすることも大切ですが、開発現場でより即効性があり、かつ、Android以外のプログラミングにも応用できる、潰しのきく技術、それがオブジェクト指向らしいコードを書く技術です。 どうしたらオブジェクト指向らしいコードを書く技術を身につけられる? ※具体的な技術的な話をすぐに読みたい方は「開発現場」の段落までスキップしてください。 昔、先輩社員に「どうしたらオブジェクト指向らしいコードを書く技術を身につけられるか」を聞いたところ「開発経験を積むこと」というあいまいな答えをもらい悩んだことがあります。この「開発経験」は自分にとって余程都合が良いプロジェクトでないと意味がありません。テスターとしてプロジェクトに参画するのはもちろん、他者が書いたコードの拡張や改修する役割で参画してもあまりよい経験にはなりません(AndroidのAPIの使い方を知るという意味では意味がありますが)。自分でソフト構成を設計できるような新規案件や大きな機能ブロックを作成できる案件でないとなかなか「オブジェクト指向らしいコードを書く技術」を磨くチャンスがないのです。しかし、多くの仕事内容は、他者が書いたコードの拡張や改修する作業です。 オブジェクト指向らしいコードを書く技術を磨くには、自分で技術の本や記事を積極的に読み、実際に自分でコードを書き、頭をトレーニングする必要があります。 おすすめのトレーニング方法 「オブジェクト指向らしいコードを書く技術」を身につける為におすすめなのが「デザインパターン」と「リファクタリング」の技術を身につけることです。どちらもオブジェクト指向らしいコードを身につける為の要素がたくさん詰まっています。 KotlinはJavaと異なり、比較的新しい言語であるためか、デザインパターンやリファクタリングに関する書籍がなかなかありません。 私自身はJavaでオブジェクト指向らしいコードの書き方を身に着け、それを他の言語にも適用する、という形でやってきました。書籍の充実という観点からすると最初にJavaを覚えることはとても良いことだと思います。しかし、ここ数年でAndroid案件はKotlinの案件が増えてきたこともあり、最初に学習を始めるプログラム言語がKotlinという方も増えてきた印象です。そこで、Kotlinでオブジェクト指向を使いこなす為のとっかかりとなる情報を提供しようと思い、本稿を書きました。 デザインパターン デザインパターンについて、もう少し掘り下げます。デザインパターンはGoFの23パターンが有名です。私個人の意見としては、最初は23種全部覚えるよりも、重要ないくつかのパターンを使いこなすのに時間を掛けた方がよいと思っています。次のパターンを優先的にマスターするのがおすすめです。 Template Methodパターン Factory Methodパターン Facadeパターン Stateパターン Builderパターン Observerパターン この中で最初に覚えた方がよいパターンは「Template Method パターン」です。プログラミングのできる方だと、わざわざデザインパターンの名前も付ける必要もないくらいオブジェクト指向の性質そのものだ、と言う方もいると思います。それくらい「基本でかつ奥義」みたいなパターンです。この「Template Method パターン」について本記事を通して学んでいきましょう。 開発現場 ※筆者注:導入部は物語風会話形式で書いてみました。 とあるソフト開発会社「エゥーゴソフト」開発チームの二人の会話から始まります。 登場人物説明 ブライト(リーダー):社会人(&開発経験)5年目。新人の頃からAndroid一筋で、厳しい先輩に教わったこともあり、現在では新規アプリ開発プロジェクトの立ち上げを任されている。 アムロ(新人女性社員):大学生の頃は主にC言語でプログラミングしていた。Kotlin言語は入門書の7割くらいは読んだが、正直オブジェクト指向と言われてもピンとこない。新人研修のときに初めてAndroidアプリの作り方を学んだ。 ブライトさんの作成依頼 ブライトリーダー:アムロさん、こんな感じのAndroidアプリを作ってほしいんだ。(といって下記のイメージを見せる) <アプリ仕様> アプリを起動すると背景が青い画面を表示し、中央に「NEXT」ボタンを表示すること 「NEXT」ボタンを押下すると、背景が黄色の画面->赤い画面->青い画面と切り替わること Backキーを押下するとアプリが終了すること(Androidアプリでデフォルトの動作のままでよい) ブライトリーダー:アムロさんはAndroidアプリ開発を新人研修で教わったみたいだけど、いきなり全部作れって言うのは厳しいと思うから、途中まで(青い画面を表示して、ボタンが押された時のイベント処理をどう書けばよいのかがわかるところまで)作ってあるから、これをベースに作ってみて。 アムロ:わかりました。作ってみます。 ベースとなるコード(ktファイル以外は省略) サンプル:ObjectOrientationKotlin_base ※サンプルはGithubに格納してあります。 BlueActivity.kt package com.eugo.objectorientationkotlin import android.graphics.Color import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity class BlueActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.layout).setBackgroundColor(Color.BLUE) findViewById<View>(R.id.button).setOnClickListener { // 別のActivityに遷移する時の処理イメージ // val intent = Intent(this, YellowActivity::class.java) // intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP // startActivity(intent) } } } 。。。。 アムロさんはAndroid Studioの操作に苦戦しつつも、アプリを作り上げました。 アムロ:できました。こんな感じでどうでしょう。(ブライトさんにAndroid端末の画面を見せながら操作する) ブライトリーダー:いいね。動作は完璧だね。ソースコードの方を見せてもらえる? アムロさんが作ったコード サンプル:ObjectOrientationKotlin_amuro BlueActivity.kt package com.eugo.objectorientationkotlin import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity class BlueActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.layout).setBackgroundColor(Color.BLUE) findViewById<View>(R.id.button).setOnClickListener { val intent = Intent(this, YellowActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP startActivity(intent) } } } YellowActivity.kt package com.eugo.objectorientationkotlin import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity class YellowActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.layout).setBackgroundColor(Color.YELLOW) findViewById<View>(R.id.button).setOnClickListener { val intent = Intent(this, RedActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP startActivity(intent) } } } RedActivity.kt package com.eugo.objectorientationkotlin import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity class RedActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) findViewById<View>(R.id.layout).setBackgroundColor(Color.RED) findViewById<View>(R.id.button).setOnClickListener { val intent = Intent(this, BlueActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP startActivity(intent) } } } ブライトリーダー:(そうきたか。。。)うーん。残念だけどお仕事の成果物としては、このままのコードではダメだね。 アムロ:えー。ボクのコードの何がダメなんですか。ちゃんと仕様通り動いているじゃないですか。 ブライトリーダー:BlueActivity.ktとYellowActivity.ktとRedActivity.ktってなんか「とてもよく似ている」よね。 アムロ:まあ、BlueActivity.ktをコピペしてYellowActivityクラスとRedActivityクラスを作って、画面の色とか遷移先のActivityクラスのコードを修正したので。 ブライトリーダー:(コピペしたって言っちゃった。正直というか。。。)似たようなコードがあるってことはもっとコードを少なくして、スッキリさせることができるはずなんだ。コードで説明するとわかりずらいから、日本語に置き換えて説明しよう。 3つのクラスの動作を日本語で表現すると、次のようになります。 この中で、クラス毎に異なる部分を赤字にします。 (1)は3つのクラスとも同じですね。(2)と(3)は赤字以外の部分は同じです。 この同じ部分(重複部分)が「無駄」と言えます。無駄な部分を取り消し線で書きます。 この取り消し線の部分がなくなったら、スッキリしたコードになると思いませんか。そこで次のように、赤字の部分を〇〇と△△に置き換えます。 3つのクラスとも同じ内容になりました。 同じ内容なら1つのクラスで表現すればいいですよね。そこで3つのクラスのスーパークラス(親クラス)というものを考えてみます。 これをソースコードで表現すれば、スッキリしたコードになります(スーパークラスのクラス名をColorActivityとします)。 青色やYelloActivityのような情報を〇〇や△△に置き換えることを「抽象化」と言います。抽象化した情報を具体化するのはサブクラス(子クラス)側に任せ、スーパークラスは決めません。ソースコードを見ていきましょう。 サンプル:ObjectOrientationKotlin_final ColorActivity.kt package com.eugo.objectorientationkotlin import android.content.Intent import android.os.Bundle import android.view.View import androidx.appcompat.app.AppCompatActivity abstract class ColorActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //(1) findViewById<View>(R.id.layout).setBackgroundColor(myColor) //(4) findViewById<View>(R.id.button).setOnClickListener { val intent = Intent(this@ColorActivity, nextClass) //(5) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP startActivity(intent) } } /** * 自Activityの色を取得する * @return 自Activityの色 */ protected abstract val myColor: Int //(2) /** * 次に遷移するActivity Classを取得する * @return 次に遷移するActivity Class */ abstract val nextClass: Class<*>? //(3) } 「(1)レイアウトを「activity_main.xml」に設定する」については、3つのクラスで同じ内容でした。同じならば、親クラスで1回書けば、子クラスに書く必要はなくなります。 getMyColor()やgetNextClass()はabstractが付いています。ColorActivityはどんな色か、どんなクラスかを具体的に決めません。決めるのはサブクラスに任せます。色や次のクラスを取得し、それを(4)や(5)で使用することだけをスーパークラスで書きます。 サブクラス(子クラス)のソースコードも見ていきましょう。 BlueActivity.kt package com.eugo.objectorientationkotlin import android.graphics.Color class BlueActivity : ColorActivity() { override val myColor: Int get() = Color.BLUE override val nextClass: Class<*> get() = YellowActivity::class.java } YellowActivity.kt package com.eugo.objectorientationkotlin import android.graphics.Color class YellowActivity : ColorActivity() { override val myColor: Int get() = Color.YELLOW override val nextClass: Class<*> get() = RedActivity::class.java } RedActivity.kt package com.eugo.objectorientationkotlin import android.graphics.Color class RedActivity : ColorActivity() { override val myColor: Int get() = Color.RED override val nextClass: Class<*> get() = BlueActivity::class.java } クラス図でクラス関係を整理しておきます。 アムロ:なるほど。たしかにスッキリした感じがしますね。BlueActivityとか差分の情報しかないって感じがすごくします。サブクラスは、Android固有のメソッドが1つも出てこないんですね。。。でもこのようなコードに改造するのは大変な気がします(というかメンドクサイ)。なんでこんなことをやる必要があるのでしょうか。 ブライトリーダー:簡単に言えば (1)「バグの量」を減らすこと (2)クラスの役割分担を明確する を行うことでメンテナンスしやすくなるんだ。メンテナンスしやすければ「楽に」働ける。 (1)は、ソースコードを人が書く以上、書く量が多ければ多い程ミスが出て、バグが生まれる可能性が高くなる、という考え方から来ている。だから基本的にコード量は少ない方がいいんだ。(Kotlinのコード量で開発工数の見積もりをしようとお客さんがいるけど、正直コード量で見積もりするのはナンセンスだよな。コピペ文化を増長するし。) (2)は、日本語の赤字をつけた例を見ればわかると思うけど、本来BlueActivityやYelloActivityは「何色か」、「次のAcitvityクラスは何か」の2つを決めるだけでいいはずなのに黒字と赤字が混ざっている。言い換えれば、Android固有のメソッド(API)を呼ぶコード(setContentView()やstartActivity())と呼ばなくてもよいコードが混ざっている。例えば、AndroidのVersionが上がってAPIの仕様変更があって動かなくなったときにどこを修正すればいいのか調査する必要が出てきたとする。この場合、アムロさんのコードでは、BlueActivity、YelloActivity、RedActivityの3つのクラスの調査する必要がある。 <ObjectOrientationKotlin_amuroのソフト構成> それに比べてObjectOrientationKotlin_finalサンプルは、ColorActivityだけを(基本的に)調査すればいいんだ。 <ObjectOrientationKotlin_finalのソフト構成> アムロさんには、ぜひ「ObjectOrientationKotlin_final」のようなコードが書けるようになってほしいね。 アムロ:わかりました。がんばります! Template Method パターンの理解を深める為のQ&A Q.Template Method パターンとは、何かを取得する処理を抽象化するパターンのことである。Yes or No A. No。抽象化の対象は動作(動詞)、つまりメソッドのことです。ただし、〇〇を取得するというメソッドの例が、Template Method パターンを理解する上で一番わかりやすい例だとは思います。 Q. 「val parent: Parent = Child()」のようなコードが出てこないんだけど、これって本当にTemplate Method パターンのサンプル? A. 今回のサンプルでは、「val parent: Parent = Child()」の部分をAndroidが行っています。Template Method パターンの例としては、微妙かもしれませんが、メソッドを抽象化する感覚を感じて頂ければと思います。プログラミング言語の入門書を読んだだけだと、なかなか「abstract」や「interface」を自分で使ってみようという気にならないと思います。Template Method パターンを通じて、「abstract」や「interface」を自分が書いているコードに適用できないか考えてみましょう。 あとがき ここまで読んでいただきありがとうございます。 ここ数年Kotlinの書籍を見てきて、なかなかオブジェクト指向の本って出ないなーと思っています。 「ないなら自分で作ればいい」がモットーの私としては、この状況を改善したいと思っています。 もし、Kotlin版の「オブジェクト指向を使いこなす本」が欲しい!と思う方がいらっしゃいましたら、この記事のコメント欄やTwitter、YouTubeなどでご要望をいただければと思います。 必ず作成することはお約束はできませんが、次の私が作成する「技術同人誌」のテーマになるかもしれません。 本記事が少しでもオブジェクト指向の理解と実戦で活かせる助けになれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DialogFragmentで5分刻みのタイムピッカー

はじめに 記事にするのを1年間忘れてたので、備忘録といっても今更役に立つやら。 一般的な手法なのかわかりませんが、 「5分刻み TimePicker」とかで検索すると、 Android標準のタイムピッカーを改造する例がちょいちょい見つかります。 しかしこのタイムピッカーが、 API30以上がターゲットの場合にクラッシュするようになりました。 https://developer.android.com/about/versions/11/non-sdk-11 APIへのアクセスが禁止されたものの中に、TimePickerがあります。 今は新しいTimePickerが提供されているので、そっちを使ってねってことみたいです。 でもダイアル式のタイムピッカーが使いたいので、 クラッシュ原因以外の部分だけ切り取って、それっぽいものに復元しました。 画面表示する都合で、 最終的に取得できる時間と分を数値にしています。 タイムピッカー TimePickerDialogFragment.java package com.example.myapplication; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.widget.Button; import android.widget.NumberPicker; import androidx.fragment.app.DialogFragment; import java.util.ArrayList; import java.util.List; public class TimePickerDialogFragment extends DialogFragment { public interface TimePickerDialogFragmentDelegate { void onClickPositiveButton(int hour, int minute); // キャンセル押した時の処理を追加したい場合はコメントを外す // void onClickNegativeButton(); } private final static int TIME_PICKER_INTERVAL = 5; //5分刻みにする private final static int MIN_VALUE_OF_HOUR = 0; private final static int MAX_VALUE_OF_HOUR = 23; private final static int MIN_VALUE_OF_MINUTE = 0; private final static int MAX_VALUE_OF_MINUTE = 55; // タイムピッカーをループさせるためのもの private final static int TIME_PICKER_THRESHOLD_HOUR_ADD = 55; private final static int TIME_PICKER_THRESHOLD_HOUR_REMOVE = -55; private final int indexForHour; private final int indexForMinute; private final TimePickerDialogFragmentDelegate delegate; public TimePickerDialogFragment(int hour, int minute, TimePickerDialogFragmentDelegate delegate) { this.delegate = delegate; this.indexForHour = hour; this.indexForMinute = minute / TIME_PICKER_INTERVAL; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View view = inflater.inflate(R.layout.fragment_timepicker_dialog, null); final NumberPicker numberPickerHour = view.findViewById(R.id.numpicker_hours); numberPickerHour.setMinValue(MIN_VALUE_OF_HOUR); numberPickerHour.setMaxValue(MAX_VALUE_OF_HOUR); numberPickerHour.setValue(indexForHour); final NumberPicker numberPickerMinutes = view.findViewById(R.id.numpicker_minutes); numberPickerMinutes.setMinValue(MIN_VALUE_OF_MINUTE); numberPickerMinutes.setMaxValue(MAX_VALUE_OF_MINUTE / TIME_PICKER_INTERVAL); numberPickerMinutes.setDisplayedValues(createDisplayedValuesForMinute()); numberPickerMinutes.setValue(indexForMinute); numberPickerMinutes.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { final int diff = (oldVal - newVal) * TIME_PICKER_INTERVAL; int val; switch (diff) { case TIME_PICKER_THRESHOLD_HOUR_ADD: val = 1; break; case TIME_PICKER_THRESHOLD_HOUR_REMOVE: val = -1; break; default: return; } numberPickerHour.setValue(numberPickerHour.getValue() + val); } }); final Button negativeButton = view.findViewById(R.id.negativeButton); final Button positiveButton = view.findViewById(R.id.positiveButton); negativeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // キャンセル押した時の処理を追加したい場合はコメントを外す // delegate.onClickNegativeButton(); dismiss(); } }); positiveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (delegate == null) { return; } delegate.onClickPositiveButton(numberPickerHour.getValue(), numberPickerMinutes.getValue() * TIME_PICKER_INTERVAL); dismiss(); } }); final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setView(view); return builder.create(); } private String[] createDisplayedValuesForMinute() { List<String> displayedValues = new ArrayList<>(); for (int i = 0; i <= MAX_VALUE_OF_MINUTE; i += TIME_PICKER_INTERVAL) { displayedValues.add(String.format( "%02d", i)); } return displayedValues.toArray(new String[0]); } } レイアウト fragment_timepicker_dialog.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="16dp" android:gravity="center" android:orientation="horizontal"> <NumberPicker android:id="@+id/numpicker_hours" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <NumberPicker android:id="@+id/numpicker_minutes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" /> </LinearLayout> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#dcdcdc" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal"> <Button android:id="@+id/negativeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="#444444" android:background="@android:color/transparent" android:text="@android:string/cancel" android:textAllCaps="false" /> <View android:layout_width="1dp" android:layout_height="match_parent" android:background="#dcdcdc" /> <Button android:id="@+id/positiveButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:textColor="#444444" android:background="@android:color/transparent" android:text="@android:string/ok" android:textAllCaps="false" /> </LinearLayout> </LinearLayout> 呼び出し元 新規プロジェクトを作って、Hello World!のところに表示してみる。 MainActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.text); final String tag = MainActivity.class.getName(); // ピッカーの初期値 final int hour = 0; final int minute = 0; final TimePickerDialogFragment dialog = new TimePickerDialogFragment(hour, minute, (hourOfDay, miniteOfDay) -> { textView.setText(String.format("%02d:%02d", hourOfDay, miniteOfDay)); }); dialog.show(getSupportFragmentManager(), tag); } 結果 分を一周すると時間が増えたり減ったりします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ヒーローズ・リーグ2021「カフェのサイズが覚えられない」

はじめに ヒーローズ・リーグ2021へ「カフェのサイズが覚えられない」を応募しました。 ダイヤモンドさんの「スタバ」ネタが好き。 チェーン店のカフェのサイズはお店毎に呼び方がちがって覚えにくい。 体でサイズを説明しながら楽しくチェーン店のカフェサイズを覚えたい。 私がダイヤモンドさんのネタをやりたかっただけとも言えます。 応募した作品 元ネタを見てからアプリのデモ動画見ると、よりアプリを楽しめます。 カフェのサイズが覚えられない 元ネタはこちら 漫才『スタバ』/コーヒーのサイズ おもしろ荘優勝ネタ/ダイヤモンド(ダイヤモンドお笑いチャンネル) アプリのデモ動画はこちら アプリを作って嬉しかったこと ダイヤモンドの野澤輸出さんからツイートにいいねをもらいました!(今はいいねは消えています...) 体験できるカフェ スターバックス タリーズコーヒー ドトールコーヒー コメダ珈琲店 出世魚 竹 相撲 大仏 開発環境 現時点ではAndroidアプリのみです。 Android Studio(Kotlin) MK Lit(Pose detection) 音声再生(Android標準) 動作フロー 人型のガイドを表示して全身がカメラの画角に入っていることを確認します。 ○のポーズを検出するまで待ちます。(左右の肩・肘・手首の角度で判定) 各サイズを指示し、全身の高さとサイズからスコアを計算します。 現時点では高さのみで、腕の動作はスコア計算には含まれない。 登録しているチェーン店を繰り返す。 全てのチェーン店が完了したら、合計スコアを表示します。 苦労した点 全身の骨格推定からサイズ判定するため、カメラで自撮りしながらのデバッグに苦労しました。 ログ出力してもどこの動作だったか分かりにくいので最後は勘でデバッグした。 テストしすぎて筋肉痛になった。 太腿ふとくなっちゃうよ! 相撲の音声再生で前頭(まえがしら)を"ぜんとう"と発音されたこと。 急遽よみがなリストを追加して音声再生するようにした。 今後追加したいカフェ ネタにある体験できるカフェを増やしたいです。 サンマルクカフェ ゴーゴーカレー コーンなど オレトク賞の決勝に選ばれて ネタアプリとして開発したので期待はしていませんでしたが、オレトク賞の決勝への招待メールが届きました。 正直、えっ?こんなネタアプリで良いのかと思いましたが、せっかくのチャンスを断る理由もありません。体験できるカフェを追加(相撲と大仏)して臨みました。 オレトク賞の決勝へ参加してみると、毎年コンテストに応募して作品スタイルを貫き通している人、パワードスーツを自作してリアルタイムに利用しながら発表する人、車のスマートキーを自作してしまう人など強者揃いで圧倒されました。私はハッカソンやコンテストでの発表経験はありましたが、子犬ちゃんの気分で久々に緊張しました。 結果は残念ながら入賞はしませんでしたが、あとから聞くとウケていたので場は暖められたと思います。 また、審査員の方からは「(ネタが好きで作ったので)これこそオレトク賞だよねー」とお褒めの言葉をいただきました。 受賞 特別賞 byヒーローズ・リーグ運営事務局をいただきました! https://protopedia.net/event/21 12/11の決勝で入賞作品が発表されました。一通り発表が終わり入賞はなかったので、みんなの作品に刺激をもらって来年も頑張ろうとツイートしてビンゴまでの休憩に入りました。 しかし、休憩が開けると、審査員から特別賞があるとアナウンスあり(気分はビンゴ待ち)、なんと「カフェのサイズが覚えられない」が特別賞を受賞しました!また来年〜とツイートした数分後だったので、驚いたのと来年こそと言いながら今年受賞してるやんと笑いました。お笑い芸人のネタをネタアプリで体験できたのが良かったです。 おわりに このアプリは家族と動画撮影に協力してくれた友人くらいしか体験できていません。オフラインで会える世の中なら、みんなに忘新年会やクリスマスパーティーでカフェのサイズを競い合ってもらいたいです。 また、ヒーローズ・リーグや動画を見た友人などから、ぜひ体験したいのでアプリをリリースして欲しいとの要望もあり、頑張ってストアに公開しようと思います。(まずはAndroidから) アプリの構想からコンテスト応募までに1ヶ月くらいかかり、構想したときより予想以上に苦労しました。もっと早くから作り始めてればと思いました。今年の作品からヒントを得たアイデアもあるので、来年もコンテストに応募して入賞したいので、年明けからネタの構想を練って走り出したいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む