フリーランス 技術調査ブログ

フリーランス/エンジニア Ruby Python Nodejs Vuejs React Dockerなどの調査技術調査の備忘録

android/kotlinで位置情報を取得する

はじめに

AndrodManifest.xml

ACCESS_COARSE_LOCATION 

  • 位置情報の精度に、街区を指定します。

ACCESS_FINE_LOCATION

  • ACCESS_COARSE_LOCATION をリクエストしたときよりも正確な位置情報を指定します。
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.INTERNET"/>

google maps keyの指定

    <application・・・>
            <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="@string/google_maps_key" />
    </activity>

layout

  • layout/activity_maps.xml
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:map="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/map"
    android:name="com.google.android.gms.maps.SupportMapFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MapsActivity" />

Activity

package biz.hogehoge.currentlocationonmap

import android.content.pm.PackageManager
import android.location.Geocoder
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices

import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import java.io.IOException
import java.util.*

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
    var currentMarker: Marker? = null
    private lateinit var mMap: GoogleMap
    var fusedLocationProviderClient: FusedLocationProviderClient? = null
    var currentLocation: Location? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        // SupportMapFragmentを取得し、マップが使えるようになったら通知を受ける。       
        fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
        fetchLocation()
    }

    private fun fetchLocation() {
        // GPSのパーミッションの確認
        if(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION)
                    != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION)
            != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION), 1000)
            return
        }

        val task = fusedLocationProviderClient?.lastLocation
        task?.addOnSuccessListener { location->
            if(location != null ) {
                this.currentLocation = location
                val mapFragment = supportFragmentManager
                    .findFragmentById(R.id.map) as SupportMapFragment
                mapFragment.getMapAsync(this)
            }

        }

    }

    // GPSの許可ダイアログの承認結果を受け取る
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
       when(requestCode) {
           1000 -> if(grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
               fetchLocation()
           }
       }
    }

    // 利用可能になったマップを操作します。
    // このコールバックは、マップが使用可能な状態になったときにトリガーされます。
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // シドニーにマーカーを追加し、カメラを動かす
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))*/


        val latlong = LatLng(currentLocation?.latitude!!, currentLocation?.longitude!!)
        drawMarker(latlong)

        mMap.setOnMarkerDragListener(object: GoogleMap.OnMarkerDragListener {
            override fun onMarkerDrag(p0: Marker?) {

            }

            override fun onMarkerDragEnd(p0: Marker?) {
                if(currentMarker != null)
                    currentMarker?.remove()

                val newLatLng = LatLng(p0?.position!!.latitude, p0?.position.longitude)
                drawMarker(newLatLng)
            }

            override fun onMarkerDragStart(p0: Marker?) {

            }
        })
    }

    // マーカー表示hyouji
    private fun drawMarker(latlong : LatLng) {
        val markerOption = MarkerOptions().position(latlong).title("現在地")
            .snippet(getTheAddress(latlong.latitude, latlong.longitude)).draggable(true)

        mMap.animateCamera(CameraUpdateFactory.newLatLng(latlong))
        mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latlong, 15f))
        currentMarker = mMap.addMarker(markerOption)
        currentMarker?.showInfoWindow()
    }

    // 現在地取得
    private fun getTheAddress(latitude: Double, longitude: Double): String? {
        val geocoder = Geocoder(this, Locale.getDefault())
        val addresses = geocoder.getFromLocation(latitude, longitude, 1)
        if(addresses.size > 0 )
            return addresses[0].getAddressLine(0)
        return ""
    }
}

実行結果

https://cdn-ak.f.st-hatena.com/images/fotolife/P/PX-WING/20210319/20210319215131.jpg

jsonからドキュメントを自動生成する

はじめに

  • jsonファイルを仕様書を動的に作成できないか色々と調べてみた。

CASE1: maticを使ってみる

  • maticを使ってjsonからドキュメントを自動生成する
npm install -g matic
npm install -g pug

www.npmjs.com

  • ドキュメントを生成するために必要なテンプレートをダンロードする
git clone https://github.com/mattyod/matic-draft4-example.git

実行結果

  • サンプルは動いたが、実際に利用しているJSONではうまく行かなかった。使えたら便利そう f:id:PX-WING:20210321002442p:plain

CASE2: json2xlsを使ってみる

  • 下記のインストールする
npm install json2xls

www.npmjs.com

jsonからエクセルファイルを出力するコード

  • test.jsファイルを作成して下記のように記述する
const jsonData = require('./hogehoge.json');
console.log(jsonData)
var json2xls = require('json2xls');
const fs = require('fs');
const filename = 'sample.xlsx';
var convert = function () {
  var xls = json2xls(jsonData);
  fs.writeFileSync(filename, xls, 'binary', (err) => {
     if (err) {
           console.log("writeFileSync :", err);
      }
    console.log( filename+" file is saved!");
 });
}

convert()

実行

node  test.js

実行結果

  • 散々な結果になってしまった。 f:id:PX-WING:20210321002958p:plain

Case3 GoogleスプレッドシートのGASを利用したサンプル

  • これを試す時間がなかったので今度試してみたいと思います。

gist.github.com

medium.com

vercelでデプロイする方法

CLIから本番環境へデプロイする

インストール

  • 下記のパッケージをインストールする
npm i -g vercel

www.npmjs.com

cliでログイン

  • 下記のコマンドを実行するとメールアドレスを聞かれるので、入力すると入力したメールアドレスに対してメールが送信されます。
vercel login
  • 受信したメールのVERIFYボタンをクリックすることでログインすることができる。 f:id:PX-WING:20210320001248p:plain

デプロイ

  • 下記のコマンドを実行するとデプロイすることができますが、本番環境にはすぐに反映されません。
vercel deploy

GUIから本番環境へデプロイする

  • 変更したPromote to Productionを選択する

f:id:PX-WING:20210319235845p:plain

  • ダイアログが表示されて再度、Promote to Productionを選択すると本番環境にデプロイされる f:id:PX-WING:20210320000916p:plain

nuxtjs/typscriptでコンポーネントを作成する

Options APIの場合

  • components/test.vueファイルを作成して下記のように記述する
<template>
    <div>
        Name: {{ fullName }}
        Message: {{ message }}
    </div>
</template>
<script lang="ts">
import Vue, { PropOptions } from 'vue'

interface User {
  firstName: string
  lastName: string
}

export default Vue.extend({
  name: 'YourComponent',

  props: {
    user: {
      type: Object,
      required: false
    } as PropOptions<User>
  },

  data () {
    return {
      message: 'This is a message'
    }
  },

  computed: {
    fullName (): string {
      return `${this.user.firstName} ${this.user.lastName}`
    }
  }
})
</script>
  • pages/index.vueに下記のように記述する
<template>
  <div class="container">
    <Test :user="user"/>
  </div>
</template>

<script>
import Test from '~/components/Test.vue'

export default {
  components: {
    Test
  },
  data() {
    return {
      user: {
        firstName: '太郎',
        lastName: '山田'
      }
    }
  },
}
</script>

Composition Apiの場合

  • plugins/composition-api.jsファイルに下記のように記述する
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Vue.use(VueCompositionApi)
  • components/hoge.vueファイルを下記のように記述する
<template>
    <div>
        CNT: {{ count }}
        <button @click="increment">ボタン2</button>
    </div>
</template>
<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

export default defineComponent({
  setup() {
    const count = ref(0);
    function increment() {
      count.value += 1;
    }
    return {
      count,
      increment,
    };
  },
});
</script>
  • pages/index.vueに下記のように記述する
<template>
  <div class="container">
    <Hoge />
  </div>
</template>

<script>
import Test from '~/components/Hoge.vue'

export default {
  components: {
    Hoge
  },
}
</script>

AndroidのTableLayout/LinearLayoutをインクルードする

作成した画面

f:id:PX-WING:20210318001628p:plain

activity_main.xml

  • このレイアウトがメインのxmlとなり、そのほかの3つのレイアウトファイルをincludeして読み込んでいる
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <include layout="@layout/fragment_main" />
    <include layout="@layout/table_layout" />
    <include layout="@layout/linear_layout" />
</androidx.constraintlayout.widget.ConstraintLayout>

linear_layout.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:text="配置のみのボタン"/>

</LinearLayout>

table_layout.xml

<TableLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TableRow>
        <!-- layout_columnで左から2番目の位置を指定 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_column="0"
            android:text="2"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="3"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="3"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="3"/>

    </TableRow>

    <TableRow>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="4"/>
        <!-- 隣のセルと結合 -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_span="2"
            android:text="5"/>
    </TableRow>

    <TableRow>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="7"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="8"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="9"/>
    </TableRow>
</TableLayout>

fragment_main.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="match_parent"
    android:background="#bdc"
    android:orientation="vertical"
    android:gravity="center" >

    <TextView
        android:text="テスト"
        android:layout_width="wrap_content"
        android:textSize="40sp"
        android:textColor="#063"
        android:layout_height="wrap_content"/>

</LinearLayout>

android/kotlin TODOアプリ 実装

はじめに

  • 前回,AndroidのTODOアプリのレイアウトを作成したので、今回は実装部分を書いてみる

px-wing.hatenablog.com

コード

  • MainActivityファイルの内容は下記のようにする
package biz.hoge.todolist
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_todo.view.*

class TodoAdapter(
        private val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        return TodoViewHolder(
                LayoutInflater.from(parent.context).inflate(
                        R.layout.item_todo,
                        parent,
                        false
                )
        )
    }

    fun addTodo(todo: Todo) {
        todos.add(todo)
        notifyItemInserted(todos.size - 1)
    }

    fun deleteDoneTodos() {
        todos.removeAll { todo ->
            todo.isChecked
        }
        notifyDataSetChanged()
    }

    private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) {
        if(isChecked) {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
        } else {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val curTodo = todos[position]
        holder.itemView.apply {
            tvTodoTitle.text = curTodo.title
            cbDone.isChecked = curTodo.isChecked
            toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
            cbDone.setOnCheckedChangeListener { _, isChecked ->
                toggleStrikeThrough(tvTodoTitle, isChecked)
                curTodo.isChecked = !curTodo.isChecked
            }
        }
    }

    override fun getItemCount(): Int {
        return todos.size
    }
}
  • ToDo.ktファイルを作成して下記のように記述する。データの型定義を指定する
package biz.hoge.todolist

data class Todo(
        val title: String,
        var isChecked: Boolean = false

)
  • TodoAdapter.ktファイルを作成して下記のように設定する
package biz.hoge.todolist
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_todo.view.*

class TodoAdapter(
        private val todos: MutableList<Todo>
) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        return TodoViewHolder(
                LayoutInflater.from(parent.context).inflate(
                        R.layout.item_todo,
                        parent,
                        false
                )
        )
    }

    fun addTodo(todo: Todo) {
        todos.add(todo)
        notifyItemInserted(todos.size - 1)
    }

    fun deleteDoneTodos() {
        todos.removeAll { todo ->
            todo.isChecked
        }
        notifyDataSetChanged()
    }

    private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) {
        if(isChecked) {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
        } else {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val curTodo = todos[position]
        holder.itemView.apply {
            tvTodoTitle.text = curTodo.title
            cbDone.isChecked = curTodo.isChecked
            toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
            cbDone.setOnCheckedChangeListener { _, isChecked ->
                toggleStrikeThrough(tvTodoTitle, isChecked)
                curTodo.isChecked = !curTodo.isChecked
            }
        }
    }

    override fun getItemCount(): Int {
        return todos.size
    }
}

android/kotlin TODOアプリ レイアウト

作成する画面

  • 今回は下記の画面をアプリを作成する。kotolin側の処理は次回に書きますが、今回はレイアウトのみ f:id:PX-WING:20210316001030p:plain

レイアウトファイル

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rvTodoItems"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/etTodoTitle"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/etTodoTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:hint="Enter Todo Title"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btnAddTodo"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnAddTodo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add Todo"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btnDeleteDoneTodos" />

    <Button
        android:id="@+id/btnDeleteDoneTodos"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Delete done"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

item_todo.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="80dp"
    android:paddingStart="8dp"
    android:paddingEnd="8dp">

    <TextView
        android:id="@+id/tvTodoTitle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Example"
        android:textSize="24sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/cbDone"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <CheckBox
        android:id="@+id/cbDone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>