RecyclerView
RecyclerViewは、多量のリストデータをセットするために考え出されたもの。限られた画面を有効利用するために作られている。
RecycleViewには専用のアダプタクラスが存在しない。だからアダプタクラスを自分で作成しなければならない。ただし、RecyclerView.Adapterを継承すれば良い。継承の際には、onCreateViewHolder(), onBindViewHolder(), getItemCount()の3つメソッドを実装しなければならない
ActionbarとToolbarの存在
Actionbar
Actionbarはデフォルトで実装されているもの。Actionbarはstyle.xmlに記述されている。
アクションバーの片側にはアクティビティのタイトルが表示される。反対側にはオーバーフローメニューが配置される。
Toolbar
アクションバーと同等の機能を有するが、toolbarの場合Activityからアクセスできるようだ。そこが大きな違いのようである。
ドキュメント
https://developer.android.com/training/appbar/setting-up?hl=ja
Androidアプリ開発 CardViewについて
CardView
djangoと違って、自分でHTMLを書かないのでBootstrapも使えるわけもない。したがってandroidアプリ開発では準備されたものから選んで使っていかなければならない。CardViewなるものがあったので調べてみる。
参考:https://developer.android.com/guide/topics/ui/layout/cardview?hl=ja
他の資料:http://itnavdev.blogspot.com/2015/04/cardview.html
http://ojed.hatenablog.com/entry/2015/12/05/161013
ドキュメント通りやるとAndroidxを使用することを求められる件
CardViewを使うために、dependenciesにimplementation 'com.android.support:cardview-v7:28.0.0'を追加することをドキュメントでは指摘している
自分がgradle.buidにこれを追加したときに、migrate to Androidxをすることを勧められた。良くもわからずAndroidxに移行することに決めた。
migrate to Androidxのやり方は簡単で、Android StudioのメニューにRefacterがある。それを開くとMigrate to Androidxという項目があるのでそれを押すだけだった。これでAndroidxの移行が済んだことになると思われる。
またandroidxに移行すると、マニフェストファイルの内容が削除される事もわかった。以下のものが消えていた。
- uses-permission android:name="android.permission.INTERNET"
- android.permission.ACCESS_NETWORK_STATE
- android:usesCleartextTraffic="true"
これら消えてしまったパーミッションを追加し直す必要もある。
Androidxに移行すると、ドキュメント通りにはCardViewを呼び出せない
ドキュメントによると、<android.support.v7.widget.CardViewのようにしてCardViewをレイアウトファイルに描画するらしいのだが、同じように試してもエラーでアプリケーションが落ちてしまう結果になってしまった。またAndroidxに移行してからgradle.buildのdependenciesの様子がおかしい。'com.android.support:cardview-v7:28.0.0'に赤の波下線がついてしまっているのだ。implementation 'androidx.cardview:cardview:1.0.0'に変更してSyncしたら赤の波下線を削除することに成功した。
layoutファイルでは、android.support.v7.widget.CardViewをandroidx.cardview.widget.CardViewに変更するとCardViewらしきものは描画する事ができた。
ライブラリの変換関係は以下を見ると調べられるようだ。
https://developer.android.com/jetpack/androidx/migrate/artifact-mappings
次の課題は表示したいオブジェクトの数に従ってどのようにcardviewを表示させる事ができるか
これを調べる。参考になるかも。https://teratail.com/questions/93023
これはActivity(.ktファイル)で実装すれば良い。例えばカードにしたい内容が6つあるとする。その場合forループを使ってCardViewオブジェクトを6回生成して、6回LinearLayoutなどにaddView()メソッドを実行すれば良いことがわかった。
CardViewの属性を調整する
まずバックグラウンドカラーを調整する。
https://developer.android.com/reference/androidx/cardview/widget/CardView.html?hl=ja#setCardBackgroundColor(android.content.res.ColorStateList)
これを利用すれば色を変えられる。
引数についてはColor.WHITEやColor.BLUEのようにすれば変更できることを確認した。
参考:https://kotaeta.com/59762937
https://tutorialmore.com/questions-1550546.htm
結論 var cv_obj = CardView(this) cv_obj.setCardBackgroundColor(Color.BLUE)
DRFチュートリアルを読んでわかったことをメモする
Tutorial 2: Requests and Responses
参照:https://www.django-rest-framework.org/tutorial/2-requests-and-responses/
RequestオブジェクトがDRFでは少し異なるらしい。
request.dataが使える。ResponseオブジェクトもDRFでは少し異なるらしい。return Response(data)のように使うらしい。
@api_viewはfunction based viewのためのもので、APIViewはclass-based viewのためのものである。@api_viewやAPIViewを使うとRequestオブジェクトもResponseオブジェクトも少々変わる。Responseオブジェクトに限って言えば、Responseオブジェクトにcotextを加えることができる。APIViewや@api_viewを使わなかった場合、contextを渡すことができない。以下の例はある。
hoge_api/views.py from django.http import JsonResponse from django.views.generic import View from items.models import Item from hoge_api.serializers import ItemSerializer class ItemListSerializerView(View): def get(self, request, *args, **kwargs): item_objects = Item.objects.all() serializer = ItemSerializer(item_objects, many=True) return JsonResponse(serializer.data, safe=False)
上記の例では、item_objectsのデータをすべてJsonResponseで表現することができる。しかしながらJsonResponseで返せるのはitem_objectsだけで、他のオブジェクト(例えば、Userオブジェクト)はJsonResponseの内容として返すことができない。つまりある特定の1種類のクエリ、またはオブジェクトしか返すことができないのがこのコードの弱点だとわかる。この弱点を克服し複数のクエリ、オブジェクトをJosnとして返す事ができるのが@api_viewやAPIViewである。
APIViewを使う理由がわかった。それはrest_framework.APIViewを使うとrest_framework.response.Responseを使えるようになることだ。これを使うと結果の表示がいい感じになる。これくらいしかわからないけどいいのか??
Djangoのアプリにrequestしてレスポンスを得る
djangoのrestapiを実装する
DRFをインストールする。
JSONのレスポンスを返す仕組みを構築する。とりあえずdjangoでJSON形式でレスポンスを返す仕組みを構築する。
Http接続を行う
基本的にAsyncTaskクラスを使って、その中のdoInBackgroundメソッド内でhttp接続を行う。したがって"http接続を行うこと"="AsyncTaskクラスを利用すること"として結びつけることが開発の手助けになる。
djangoはデプロイした状態ではhttp(s)接続するのは容易だが、ローカルホストに接続するのはいくらか注意点がある。
1に関しては、以下に資料がある。
https://stackoverflow.com/questions/57711124/how-to-connect-a-django-rest-framework-into-android-app-in-testing-environment
要するに、エミュレーターを使ってそこから127.0.0.1に接続することはエミュレーターの内部に接続してしまうことになる。python manage.py runserver を行ったローカルのPCに接続するには10.0.2.2に接続する必要がある。したがってurlの文字列は"http://10.0.2.2:8000/api/no/path"と記述しなければならない。
2に関しては、settings.pyのALLOWED_HOSTに10.0.2.2の内容を記述していないと、せっかくdjangoに接続してもdjangoのほうから接続を切られてしまう結果になる。ちなみにdjangoのターミナルでは以下のように表示されたらALLOWED_HOSTに追加する。
Invalid HTTP_HOST header: '10.0.2.2:8000'. You may need to add '10.0.2.2' to ALLOWED_HOSTS.
設計イメージ
ActivityMain.xmlにListViewを配置する。このListViewの要素をタップすると、次の画面に遷移する。次の画面はNextActivityと命名する。NextActivityに画面遷移したら、まずAcyncTaskクラスのdoInBackgroundメソッド(オーバーライド済み)が動く。このdoInBackgroundメソッド内でhttp(s)接続を行い、JSONレスポンスを取得する。doinBackgroundメソッドが終了したらonPostExecuteメソッドで取得したJSONレスポンスを画面遷移先に表示する。
JSONオブジェクトを解析する
val json_date = JSONObject(result) //val item_object_count = json_data.getJSONObject("item_objects.count") val item_object_count = json_data.getString("item_objects.count") val tv_show = findViewById(R.id.TV_show) tv_show.text = item_object_count
ウェブアプリを作成する際に決定すべき事項
考慮事項
- サーバーをどこに置くか
- 最初に設計図を作っておく
- 設計図に基づいてテストを構築する
ウェブアプリケーションを海外で使う想定とするならば、できるだけ近い位置でサーバーを配置するべきである。
通信はパケット通信なので、大きいデータを送受信する場合には端末とサーバーのやり取りの回数が多くなってしまう。サーバーとの距離が遠い場合、ひとつひとつのパケット通信に時間がかかってしまう。そのため大きい容量の通信を行うためには、サーバーを近くに設置する必要がある。
簡単なアプリケーションだからといって、設計図を作らないで開発を進めてしまうと問題が生じる。
アプリケーションで使われる変数が他の機能とかぶってしまったり、理解しづらくなる場合もある。また、変数を変更するたびに当該変数を含むロジックに影響が出てしまい、その修正にとてもコストがかかる。
webアプリケーションにSNSシェアボタンを実装する
SNSシェアボタンが必要になったわけ
自分のwebサービスを認知してもらうためにはネット上では拡散して認知してもらうのが良いと考える。そのためには自分のwebサービスをSNSでシェアしてもらうのが良いだろう。そこでSNSシェアボタンを実装する方法を調べる。
参考URL
https://littlethings.jp/blog/web/sharebutton-html
https://narito.ninja/blog/detail/96/
ボタンの実装方法
twitterやFacebookのSNSシェアボタンを実装する。Facebookのシェアに関しては、developerサイトにドキュメントがある。
https://developers.facebook.com/docs/plugins/share-button?locale=ja_JP
埋め込みのボタンはどうやら重いというデメリットがあるようだ。その他のシェアボタンの実装としてアンカータグ内のhref属性を設定することでも実装できるようだ。今回はこちらの方法を採用する。
またdjangoではSNSシェアボタンの実装に際してサードパーティによるライブラリを確認する事ができた。こちらも使える可能性がある。
https://pypi.org/project/django-social-share/
Facebookのアンカータグ
<a href="http://www.facebook.com/share.php?u=「シェアしたいURL」&t=「タイトル」" target="_blank"> Facebookでシェア</a>
シェアしたいURLを準備する
シェアしたいURLを準備するわけだけど、djangoでシェアするURLは以下のような構成である。
https://projectdomain/items/detail/3
projectdomain部分については、{{ request.get_host }}で取得する事ができる。
https部分については、{{ request.scheme }}で取得する事ができる。
path部分については、{{ request.path }}で取得する事ができるし、また{% url 'items:detail' item_obj.id %}みたいなことでもpathは取得することができる。
{{ request.scheme }}://{{ request.get_host }}{{ request.path }}
上記のようにすればURLは作成でき、以下のように利用すれば良い。
http://www.facebook.com/share.php?u={{ request.scheme }}://{{ request.get_host }}{{ request.path }}
シェアしたいタイトルを準備する
タイトルもdjangoのテンプレートタグシステムを利用すればよいだろう。
{{ item_obj.title }}みたいな感じでやったらどうか?
<a href="http://www.facebook.com/share.php?u={{ request.scheme }}://{{ request.get_host }}{{ request.path }}&t={{ item_obj.title }}" target="_blank"> Facebookでシェア</a>
Facebookっぽいボタンを準備する
BootstrapとFontawesomeを利用したソーシャルボタンが有る。これを利用すれば、見かけは良くなる。
https://github.com/lipis/bootstrap-social
<a class="btn btn-block btn-social btn-facebook" href="FacebookのシェアボタンのURL"><span class="fa fa-facebook"></span>Facebookでシェア</a>
Twitterのアンカータグ
<a href="https://twitter.com/share?url=「シェアしたいURL」" target="_blank" rel="nofollow"> </a>
djangoのテンプレートシステムを使った場合
<a href="https://twitter.com/share?url={{ request.scheme }}://{{ request.get_host }}{{ request.path }}" target="_blank" rel="nofollow"> </a>
bootstrap-socialを使った場合は以下のようになる。
<a class="btn btn-block btn-social btn-twitter mt-1" href="https://twitter.com/share?url={{ request.scheme }}://{{ request.get_host }}{{ request.path }}" target="_blank" rel="nofollow"><span class="fa fa-twitter"></span>Tweet </a>
nginxファイルサイズによるエラー
nginx
カメラを使う
カメラアプリを起動する
カメラを起動するにはintentオブジェクトを生成し、アクティビティを起動する必要がある。また以下の2行をボタンやImageViewのタップと同時に起動する仕組みにしておくと良い。またカメラに関してはユーザーのパーミッションやマニフェストファイルへのカメラ使用の宣言は不必要となっている。
カメラアプリを起動するコード //intentオブジェクトを生成する方法 val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) startActivityForResult(intent, 200)
カメラアプリ終了後に起動元アクティビティに戻したい場合にはstartActivityForResult()を利用するとよい。つまりカメラアプリを使ったあとに自動的にカメラアプリ起動前のアプリに移動させる仕組みをstartActivityForResultが担っている。
またstartActivityForResultには、ついになるメソッドがある。これはカメラアプリ起動前のアプリに再び戻ってきたときに実行されるメソッドである。メソッド名は、onActivityResult()である。
カメラで撮った画像をストレージに保存する
画像をストレージに保存する場合には、ストレージ利用のためのパーミッションを許可する必要がある。ということはマニフェストファイルにその旨を記述すること、ユーザーに許可をとるダイアログの表示が必要だと思われる。
マニフェストファイルには以下のように記述する。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
ユーザーにパーミッションの許可を取るダイアログを表示する。
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSON_GRANTED) { val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) ActivityCompat.requestPermissions(this, permissions, 2000) return }
ストレージの画像をImageViewで表示するために使うもの
ImageViewオブジェクト.setImageURI(表示したい画像のUri)で表示することができる。
具体的に表示させる手続きを言えば、
- まず表示したい画像のUriオブジェクトを生成する。
- 表示したいレイアウトファイルのImageViewをzfindViewByIdで取得する。
- ImageViewオブジェクト.setImageURI(表示したい画像のUri)をアクティビティ(.ktファイル)に記述する。
コードに起こすと以下のようになる。
val _imageUri = 画像uri作成コード val showImage = findViewById<ImageView>(R.id.showImage) showImage.setImageURI(_imageUri)
ImageViewのその他使い方
ImageViewはレイアウトファイル内にて、android:onClick属性を設定する事ができるようだ。例えばandroid:onClickを設定すると、ImageView全体をタップするとカメラアプリを起動する仕組みを実装する事ができる。
表示するソースの指定(htmlでいうimgタグsrc属性)はImageViewで行われる。属性は以下となる。
app:srcCompat="@android:drawable/ic_menu_camera"
撮影した画像をストレージに保存する方法
カメラアプリを起動するintentオブジェクトにUriオブジェクトをExtraデータに埋め込むだけで良い。
intent.putExtra(MediaStore.EXTRA_OUTPUT, _imageUri)
_imageUriはUriオブジェクトであり、これはcontentResolverオブジェクトのinsertメソッドを用いて作成する必要がある。
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values )
valuesはContentValuesオブジェクトであり、Uriオブジェクトを生成するにはContentValuesインスタンスを生成する必要がある。
val values = ContentValues() //画像ファイル名を設定 values.put(MediaStore.Images.Media.TITLE, filename ) //画像ファイルの種類を設定 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
まとめ
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSON_GRANTED) { val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE) ActivityCompat.requestPermissions(this, permissions, 2000) return } val dateFormat = SimpleDateFormat("yyyyMMddHHmmss") //現在の日時を取得 val now = Date() val nowStr = dateFormat.format(now) val filename = "UseCameraActvityFoto_${nowStr}.jpg" val values = ContentValues() //画像ファイル名を設定 values.put(MediaStore.Images.Media.TITLE, filename ) //画像ファイルの種類を設定 values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") _imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values ) //intentオブジェクトを作成 val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) intent.putExtra(MediaStore.EXTRA_OUTPUT, _imageUri) //アクティビティを起動 startActivityForResult(intent, 125)
指定した緯度、経度を地図に表示させる
Intentオブジェクトを生成して、地図のURIを渡してやれば地図を表示することができる。uriの構成は、"geo:緯度,経度"であるのでintent=Intent(Intent.ACTION_VIEW, uri)でIntentオブジェクトを生成することができる。
//緯度、経度をもとにURI文字列を作成する val uriStr = "geo:${_latitude},${_longitude}" //URIオブジェクトを生成する val uri = Uri.parse(uriStr) //Intentオブジェクトを生成 val intent = Intent(Intent.ACTION_VIEW, uri) //アクティビティを起動する startActivity(intent)
//LocationManagerオブジェクトを取得する val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager val locationListener = GPSLocationListener() //位置情報の更新 locationManager.requestLocationUpdates(locationManager.GPS_PROVIDER, 0, Of, locationListener)
GPSの機能を利用するために許可を与える <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
permissonの許可をユーザーに聞かないといけない件
androidのアプリでは、マニフェストファイルにpermisson許可を与える記述に加えて、ユーザーにpermisson許可を得る必要があるようだ。
そこでonCreateメソットでまずアプリの許可がされているかまず確認する。そして許可が得られていない場合には、許可を得るロジックを記述する必要がある。
アプリの許可がされているかどうかを確認する方法はActivityCompat.checkSelfPermisson()メソッドを使う。メソッドの引数は2つある。applicationContextとManifest.permission.ACCESS_FINE_LOCATIONである。ちなみにACCESS_FINE_LOCATIONはマニフェストファイルに記述したキーワードである。
戻り値はPERMISSION_GRANTED又はPERMISSION_DENIEDが返される。
通知を実装する
通知を行うために必要なこと
NotificationManagerオブジェクトに通知チャネルを設定すること(通知の環境設定)と、通知オブジェクトを作成し、作成した通知オブジェクトをNotificationManagerを通じて通知させる。
通知の環境設定(準備)
NotificationMnagerオブジェクトを作成し、次に通知チャネルを作成した後、通知チャネルをNotificationManagerオブジェクトに渡す。
通知の環境設定(準備) override fun onCreate(){ //NotificationManagerオブジェクトを作成 val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // id① val id = "notification_test_channel" // name② val name = getString(R.tring.hoge) // importance③ val importance = NotificationManager.IMPORTANCE_DEFAULT //①②③ を使って通知チャネルを生成 val channel = NotificationChannel(id, name, importance) manager.createNotificationChannel(channel) }
通知する
通知オブジェクトを生成し、NotificationManagerオブジェクトのnotify()メソッドを実行し、通知する。
通知オブジェクトはメニューを実装するときのように直接生成するのではなく、Builderクラスを利用して通知オブジェクトを生成する。
通知する
メニューを実装する(EmptyActivity)
まずメニュー用の.xmlファイルを格納するディレクトリを作成する
resディレクトリ内にmenuディレクトリを作成することにする。その手続は以下の通りである。
resディレクトリを右クリックして、"New"を選択する。そして、"Android Resource Directory"を選択肢、"Resource type: "からmenuを選択。これでディレクトリを作成することができた。
menu用の.xmlファイルを作成する
作成したmenuディレクトリを右クリックし、"New","Menu resource file"を選択する。
.xmlファイルの構成
"menu"タグではじめ、そのタグの間に"item"タグを記入する構成になっている。
itemタグ内に記述する属性がある。
属性app:showAsActionには3つの属性値がある。
- never
- always
- ifRoom
アクティビティ(.ktファイル)にメソッドを追加する
.ktファイルにonCreateOptionMenu()メソッドを追加する。
戻るメニューを実装する場合
ロジック自体は、選択されたメニューが「戻る」ならば、finish()を実行する。これをonOptionsItemSelected()メソッドに挿入する。
Androidアプリの画面遷移を実装する
画面遷移のコードを記述する場所
MainActivity等のHogeActivity:AppCompatActivity(){}にintentに関するロジックが入ったリスナを追記する。
リスナクラス内の画面遷移ロジックの書き方
画面遷移のロジックのイメージは以下の3点になる。
Intentクラスのインスタンスを生成する
Intentクラスに引き渡す引数は2つある。
packageContext:Contextとcls:Classである。packageContextの例としては、applicationContextがある。
clsに関しては書き方に気をつける必要がある。「クラス名::class.java」と記述する。例:"MainActivity::class.java"
val intent = Intent(applicationContext, NextScreenActivity::class.java) startActivity(intent)
遷移先画面に渡すデータをintentインスタンス格納する
インスタンスにデータを格納する事のほか、格納したデータの取得もすることもできる。画面遷移前には、putExtra()メソッドを使いデータをインスタンスに格納する。画面遷移後にはgetStringExtra()メソッドやgetIntExtra()メソッドを使ってデータを取得する。
遷移先の画面から戻るボタンを実装する
遷移したActivityファイル(.ktファイル)にコードを記述すれば遷移前の画面に戻すことができる。
fun onBackButtonClick(view: View){ finish() }