エラー: Methods annotated with @Insert can return either void, long, Long, long[], Long[] or List. public abstract java.lang.Object insertOne(@org.jetbrains.annotations.NotNull()

Android Kotlinでコルーチンに取り組んでおります。

コードは断片的になっちゃうんですけど、下記の方法でコルーチンでDBにデータを追加するのをやってみました。

ボタンをタップすると、データが追加されるというものです。

//Purpose.kt
@Entity
data class Purpose constructor(
    @PrimaryKey val purposeId: Int = 0,
    val purpose: String
)
//PurposeDao.kt
@Dao
interface PurposeDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOne(purpose: Purpose)
}
//TaskViewModel.kt
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {

    companion object {
        val FACTORY = singleArgViewModelFactory(::TaskViewModel)
    }

    // Create a LiveData with a String
    val currentTask: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }


    fun onButtonClicked(context: Context) {
        val coroutine_a = addTask(context)
    }

    //コルーチンのテスト
    fun addTask(context: Context) {

        viewModelScope.launch {

            addTaskDb(context)

        }
    }

    suspend fun addTaskDb(context: Context) {
        try {

            repository.addTask(context)

        } catch (cause: Throwable) {
            // If anything throws an exception, inform the caller
            Log.e("TaskViewModel", "エラー でーたべーす")
            Toast.makeText(context, "データベースエラーで更新できませんでした!", Toast.LENGTH_LONG).show();
        }
    }

}
//TaskRepository.kt
class TaskRepository(val purposeDao: PurposeDao) {

    suspend fun addTask(context:Context) {

        try {

           //コルーチンのテストのために、とりあえずデータを指定
            val purpose = Purpose(3, "がんばる");

            purposeDao.insertOne(purpose)

        } catch (cause: Throwable) {

            Log.e("TaskRepository", cause.toString())
            Toast.makeText(context, "Agent already exists!", Toast.LENGTH_LONG).show();

        }
    }
}
//MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.content_main)


        val database = getDatabase(this)
        val repository = TaskRepository( database.purposeDao)

        // Get the ViewModel.
        model = ViewModelProviders
            .of(this, TaskViewModel.FACTORY(repository))
            .get(TaskViewModel::class.java)

	//中略
        }
//AppDatabase.kt
@Database(entities = [Purpose::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
    //abstract fun purposeDao(): PurposeDao
    abstract val purposeDao: PurposeDao
}

private lateinit var INSTANCE: AppDatabase

fun getDatabase(context: Context): AppDatabase {
    synchronized(AppDatabase::class) {
        if (!::INSTANCE.isInitialized) {
            INSTANCE = Room
                .databaseBuilder(
                        context.applicationContext,
                        AppDatabase::class.java,
                        "task-admin"
                )
                .fallbackToDestructiveMigration()
                .build()
        }
    }
    return INSTANCE
}

そしたら、インストール時に上記のエラーが出て、ビルドできません。(´ω`)

色々と悩みましたが、アプリケーションのbuild.gradleを下記に変更したらできるようになりました。

annotationProcessor 'androidx.room:room-compiler:2.0.0'
implementation "androidx.room:room-ktx:2.0.0"

↓ 上記を下記に変更

annotationProcessor 'androidx.room:room-compiler:2.2.2'
implementation "androidx.room:room-ktx:2.2.2"
ロリポップのドロイド君。懐かしい。

AndroidXへの移行

前回、下記のエントリを書きましたが、

Android コルーチンを便利に利用するためのviewModelScopeが読み込めない

その後、下記のようなエラーが出て、ビルドできなくなりました。

java.lang.RuntimeException: Manifest merger failed : Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory)

AndroidXを使おうと思うと、gradle.propertiesに

android.enableJetifier=true
android.useAndroidX=true

と書くと、今までのライブラリが使えなくなります。
たとえば

import android.support.v7.app.AppCompatActivity

のままではAppCompatActivityが使えなくなり

import androidx.appcompat.app.AppCompatActivity

に置き換えないといけません。
これらを全部importするところは手でやらないといけないので、パッケージ内のコード量が多い場合は、よく考えないといけないですよ!!?

さっきの

という記事での、このようなimport時に古いAPIを読み込んじゃうようなことは防げますね。

ちな、下記のAndroidの公式サイトでは

https://developer.android.com/jetpack/androidx/migrate?hl=ja

Android Studio 3.2 以上では AndroidX を使用して既存のプロジェクトを迅速に移行できるようになりました。移行するには、メニューバーから [Refactor] > [Migrate to AndroidX] を選択します。

って書いてあるんですが、やるとbuild.gradleをきれいにしてくれるのと、レイアウトのxmlファイルを書き直してくれます。xmlファイルは

<android.support.v4.widget.DrawerLayout

<androidx.core.widget.DrawerLayout

のような書き直しを全部でやらないといけないので、自動でやってくれるのは大変助かります。

しかし、プログラム部分は手動で新しいAPIをインポートしないといけないのは変わりありませんでした…。(>_<)

それにしても、こういうビルドできない問題にかける時間や苦しみがすごいですよね。

早く、俺にプログラム書かせてくれってばよ。

というわけで、これからAndroidアプリを作ろうという人は、最初っからAndroidXで作った方がいいです。(重要なことなので、太字にしました)

令和2年も弊社をよろしくお願いいたします。

Android コルーチンを便利に利用するためのviewModelScopeが読み込めない

Kotlinのコルーチンについて知りたくって、下記のGoogleが提供しているCodeLabというサイトで勉強しておりますが、

https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#6

ここにある、

viewModelScope

というのを使いたくってですね、まずはアプリのbuild.gradleでこうしました。

implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.1.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0'
implementation "android.arch.lifecycle:extensions:1.1.1"

ViewModel内で下記のように書きます

class TaskViewModel(private val repository: TaskRepository):ViewModel() {
//中略
    
//コルーチンのテスト
    fun  addTask(context: Context) {

        viewModelScope.launch {

	//中略
        }
    }
}

上記のviewModelScopeで

unresolved reference  viewModelScope

ってなっちゃいます。

あれーと思い、サンプルを見て、import部分に手動で下記をコピペ。

import androidx.lifecycle.viewModelScope

すると、下記のエラーメッセージに変わりました。

Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val ViewModel.viewModelScope: CoroutineScope defined in androidx.lifecycle

「ははぁ。レシーバータイプが悪いのか??」

と思ってしばらく調べても、悪いところがどこかわかりませんでした。

「詰んだか…」

としょんぼりしておりました。viewModelScope自体、おニューな機能なんですよ。下記のGoogleのブログでアルファ版が紹介されているのが2019年4月なので。

Android で簡単コルーチン: viewModelScope

ちな、これを書いている2020年1月9日時点では、もう AndroidX Lifecycle v2.1.0 としてリリースされています。

build.gradleに

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"

とやっている部分です。

が、ふと思い当たって自分のソースコードとサンプルをよく見比べてみると

自ソースコードのimport部分

import android.arch.lifecycle.ViewModel

サンプルのimport部分

import androidx.lifecycle.ViewModel

アッ…。

import androidx.lifecycle.ViewModel

上記に変更したらちゃんと読み込めました~(>_<)涙。

これねー。Androidあるあるですけどねー。

importから間違っていると、なかなかわからないんですよ。

ViewModelのソースコード書いた時は、ViewModelScope使う予定はなかったので、普通にAlt+Enterで
import android.arch.lifecycle.ViewModel
されちゃうんですよね。

しかし、後でViewModelScope使うためには、ViewModelは
androidx.lifecycle.ViewModel
を使わないといけなかったんですが、 ViewModel はもう読み込めているのでわからなかったんですね。

ところで、この話は続きがあります。続きはこちら。

まだまだ続くんじゃよ。

2020年もよろしくお願いいたします!

Android Stethoを使ってアプリのDBの中身を見る(Kotlin)

Stethoという素晴らしいライブラリがあります。

http://facebook.github.io/stetho/

Facebookさんが作ってくれていて、Androidの開発の手助けをしてくれます。

今回は、DBの中身を見る、ということだけが目的で使ってみます。

シンプルに簡単にやるのが目的なので、デバッグビルドの場合だけStethoを動かすとかではありません

①導入する

アプリのbuild.gradleの dependenciesブロック内 に下記のように書きます。

dependencies{
implementation 'com.facebook.stetho:stetho:1.5.1'
}

②Stethoでアプリの中身を見られるようにする

アクティビティ内で下記のようにします。

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Stetho.initialize(
            Stetho.newInitializerBuilder(applicationContext)
                .enableDumpapp(Stetho.defaultDumperPluginsProvider(applicationContext))
                .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(applicationContext))
                .build()
        )
   //中略
}

最初、

        Stetho.initializeWithDefaults(this)

だけでいいのかな?と思ってましたが、dumpappってのやらないとDBの中身見れません。

余談ですが、 dumpappってやると、鳥のウソの画像出てくるんですが、なんででしょうね?

④Stethoを使う

Chromeを起動して、下記のようにURLに打ち込みます。

chrome://inspect/#devices

USBで接続しているデバイスが見られます。

んで、アプリ名の下にinspectというリンクが出ますので、それをクリックします。

DevtoolというのがChromeの別ウィンドウで立ち上がります。

Resourcesというタブを開き、左のWeb SQLというところをクリックします。

おおー。中身が見れる―。

ChromeでStethoでデータベースの中身を見れます。
StethoでAndroidアプリのデータベースの中身を見れます。

感激ですね!!o(>▽<)o

ちな、なぜWeb SQLなんでしょう?誰か知ってたら教えてください。┌o ペコッ

Fatal error: Uncaught –> Smarty: undefined extension class ‘Smarty_Internal_Method_Register_Function’

明けましておめでとうございます。今年もよろしくお願いいたします。

さて、新年早々、上記のエラーで環境構築できなくて、2時間ほどはまった(´;ω;`)

どっちかというと社内向けメモですがご容赦ください。

多分、こんなことではまる人も少ないとは思うんだけど、書いておきます。

環境は、WindowsにXAMPP for Windows 7.2.26 です。

PHPはPHP Version 7.2.26。

PHPのテンプレートエンジン、Smartyを呼び出すところは下記の通り。


<?php
/**
 * Class Template
 * テンプレートエンジン管理
 * @since 2018/12/06
 */
class Template {
	
    private static $smarty = null;
    
    /**
	 *  Smarty取得
	 *  @param string $template_dir templateディレクトリ
	 *  @param string $compile_dir compileディレクトリ
     *
     *  @return object Smarty  
	 */
    public static function getSmarty(
        $template_dir = TEMPLATE_DIR,
        $compile_dir = TEMPLATE_COMPILE
    ) {
        if(self::$smarty !== null) {
            return self::$smarty;
        }

        self::$smarty = new Smarty();
        self::$smarty->template_dir = $template_dir;
        self::$smarty->compile_dir = $compile_dir;

        self::$smarty->register_function('autoversion', 'Template::autoVersion');
        
        return self::$smarty;
    }

    /**
	 *  ファイル名にバージョン管理用タイムスタンプ追加
	 *  @param array $params 
     *      $params['file'] アプリのルートディレクトリからのファイル相対パス
	 *  @param object Smarty
     * 
     *  @return string クエリーストリングにタイムスタンプを追加したファイル名  
	 */
    public static function autoVersion($params, &$smarty)
    {
        $file = $params['file'];
        $path = SERVER_PATH . $file;
        if(!file_exists($path)) {
            return $file;
        }

        $mtime = filemtime($path);
        return $file . '?' . $mtime;
    }

    
}

Smartyを取得するところ。

$smarty = Template::getSmarty();

そしたら

Fatal error: Uncaught –> Smarty: undefined extension class ‘Smarty_Internal_Method_Register_Function’

で止まってしまうんですよねー。

ググっても、下記のページしか出てこず。

https://www.smarty.net/forums/viewtopic.php?p=94694&sid=369b32fbbe502599af4fa43ef106a2c2

Smartyは3.1.34(これを執筆当時の最新版)で最新版なのになー。

Smartyの設定が悪いのかな?

と思って試行錯誤すること数時間。

詰まって上記のSmarty呼び出し部分を書いた人に聞いたら、これ、Smarty2系でしか動作しない書き方らしい…。

なので、Smarty2.6.31をダウンロードし、インストールしたら動作するようになりました。

Smartyのダウンロードサイトはこちら。

https://www.smarty.net/download

人に聞くって大事だね(´ω`)