Maps Android API: Google Maps Android API v2 only supports devices with OpenGL ES 2.0 and above

Androidgoogle mapを表示しようとしてこういったエラーが出る

E/Google Maps Android API: Google Maps Android API v2 only supports devices with OpenGL ES 2.0 and above
E/eglCodecCommon: glUtilsParamSize: unknow param 0x00008cdf

何故かエミュレータGPUがautomaticだと発生するみたい。だからNexus5とかpixelとかautomaticから変更できないものは回避できないっぽい。Nexus6だと変更できるのでそちらのAVDを利用しよう。

参考:eglCodecCommon: glUtilsParamSize: unknow param プログラミング講座 スマホアプリ開発 Android Kotlin

kotlinで複数のAsyncTaskLoaderでカジュアルにview操作を行う

複数のAsyncTaskLoaderを実行して非同期でview操作を行いたかったので。thread立ててhandlerで処理すれば手っ取り早いのだが、Androidでthreadを乱発させるのはお作法が悪いようなのでAsyncTaskLoaderでやることにした。

ベースとなるAsyncTaskLoaderの実装
class MyAsyncTaskLoader(context: Context, val backgroundFunc:(()->String?)? = null): AsyncTaskLoader<String>(context){
    override fun loadInBackground(): String? {
        backgroundFunc?.let{
            return it()
        }
        return null
    }
    override fun onStartLoading(){
        forceLoad()
    }
}

コンストラクタでloadInBackground内で処理するクロージャを受け取るようにした。戻り値はloadInBackgroundの戻り値と同様のStringのオプショナルにしたが、このあたりは適宜使い方に合わせて変更したり、loadInBackgroundを実装したり、継承したり自由にすればいいかな。

LoaderManager.LoaderCallbacks の実装

サンプルコードではよくActivityのインターフェースとして実装されているが、カジュアルに非同期処理を行いたかったので、別インスタンスとして使用できるよう切り出しを行った。

class MyLoaderManager(
    val context: Context,
    val loaderManager: LoaderManager,
    val createFunc:(()->String?)? = null,
    val finishFunc:((data:String?)->Unit)? = null )
        : LoaderManager.LoaderCallbacks<String>{

    fun initLoader(i:Int,bundle: Bundle?){
        loaderManager.initLoader(i, bundle, this)
    }
    override fun onCreateLoader(p0: Int, p1: Bundle?):Loader<String>{
        return MyAsyncTaskLoader(context, createFunc)
    }
    override fun onLoadFinished(p0: Loader<String>, p1: String?) {
        finishFunc?.let {
            it(p1)
        }
    }
    override fun onLoaderReset(p0: Loader<String>) {
    }
}

こちらはコンストラクタにonCreateLoaderに渡すクロージャー( = loadInBackgroundで実行する処理 )とonLoadFinishedで実行するクロージャーを渡す。実装はActivity側に記述することでカジュアルにインスタンスを利用することができる。で、いつもはActivity側で実行するinitLoaderをこちらのクラスに移管した。

Activity側で利用はこんな感じ
class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val lm = LoaderManager.getInstance(this)

        //スレッドAはlayoutIdAのviewを操作
        val loaderA = MyLoaderManager( this, lm, {
            //loadInBackgroundで実行したい処理
            "String Data"
        },{
            //onLoadFinishedで実行したい処理
            //ここではviewの操作 String Dataと表示
            layoutIdA.text = it
        })
        loaderA.initLoader( 1, null )

        //スレッドBはlayoutIdBのviewを操作
        val loaderB = MyLoaderManager( this, lm, {
            //loadInBackgroundで実行したい処理
            "スレッドB Data"
        },{
            //スレッドB Dataと表示
            layoutIdB.text = it
        })
        loaderB.initLoader( 2, null )
    }
}

LoaderManager.getInstance(this)に渡すcontextはFragmentActivityのコンテキストじゃないと叱られるようなので

class MainActivity : FragmentActivity()

と継承するようにした。 やりたかったことは単純にコールバックそのものをActivityで実装するんじゃなくてswiftライクに必要な処理だけコールバックに追加したかった。という話し。

例えば通信したで取得したデータをviewに表示する。

    val lm = LoaderManager.getInstance(this)
    val loader = MyLoaderManager( this, lm, {
        val connection = URL("https://yahoo.co.jp").openConnection() as HttpsURLConnection
        connection.connect()
        val stringBuilder = StringBuilder()
        InputStreamReader( BufferedInputStream(connection.inputStream) ).forEachLine {
            stringBuilder.append(it)
        }
        stringBuilder.toString()
    },{
        textViewID.text = it
    })

とかすると、サクっとtextViewに表示できたりする。まあこの例だけでいうとretrofit使おうよという話しなんだけど(^_^;)

kotlinでListViewのカスタムlayout

SimpleAdapterの第5引数はintArrayOfじゃないと怒られる。

var data = arrayListOf(
    hashMapOf<String,String>("title" to "AAA", "tag" to "a", "desc" to "111"),
    hashMapOf<String,String>("title" to "BBB", "tag" to "b", "desc" to "222"),
    hashMapOf<String,String>("title" to "CCC", "tag" to "c", "desc" to "333")
)
val adapter = SimpleAdapter(
    this, data, R.layout.list_item,
     arrayOf("title", "tag", "desc"), intArrayOf(R.id.title, R.id.tag, R.id.desc)
)
val listView = findViewById<ListView>(R.id.list)
listView.adapter = adapter

kotlinでsetOnScrollListener

サンプルはListViewの末尾のRowAtが10の時にLog.dを吐く

p1は表示中の先頭のRowAt(他Viewに潜っている時も表示中とみなす), p2は表示中のRowCount, p3はListViewに渡されたArrayAdapterのArrayCount

よって、末尾のRowAtを求めるには p1+p2になる。

list.setOnScrollListener(
    object: AbsListView.OnScrollListener{
        override fun onScroll(p0: AbsListView?, p1: Int, p2: Int, p3: Int) {
            val lastIndex = p1+p2
            if(  lastIndex == 10 ){
                Log.d( "tag", "scroll" )
            }
        }
        override fun onScrollStateChanged(p0: AbsListView?, p1: Int) {
        }
    }
)

kotlinでMultiChoiceModeListener

ListViewのandroid:choiceMode="multipleChoiceModal" のやつ

list.setMultiChoiceModeListener(
    object: AbsListView.MultiChoiceModeListener{
         override fun onCreateActionMode(p0: ActionMode?, p1: Menu?): Boolean {
             return true
         }

         override fun onPrepareActionMode(p0: ActionMode?, p1: Menu?): Boolean {
             return true
         }

         override fun onItemCheckedStateChanged(p0: ActionMode?, p1: Int, p2: Long, p3: Boolean) {
         }

         override fun onActionItemClicked(p0: ActionMode?, p1: MenuItem?): Boolean {
             return true
         }

         override fun onDestroyActionMode(p0: ActionMode?) {
             var i=0
             while(i < list.childCount){
                 Toast.makeText( this@MainActivity, (list.getChildAt(i) as TextView).text, Toast.LENGTH_SHORT ).show()
                 i++
             }
         }
     }
 )