Good evening! I can't output files in a specific directory on an android device, and I can't solve it.
Here is the program code
package com.example.nt_music
import android.os.Bundle
import android.os.Environment
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.util.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println()
//readSongs(dir, ext)
val gpath: String = Environment.getExternalStorageDirectory().absolutePath
val spath = "Music"
val fullpath = File(gpath + File.separator + spath)
println("fullpath")
println(fullpath)
imageReaderNew(fullpath)
}
fun imageReaderNew(root: File) {
val fileList: ArrayList<File> = ArrayList()
val listAllFiles = root.listFiles()
if (listAllFiles == null) println("null")
if (listAllFiles != null && listAllFiles.size > 0) {
for (currentFile in listAllFiles) {
if (currentFile.name.endsWith(".mp3")) {
// File absolute path
println("downloadFilePath")
println( currentFile.getAbsolutePath())
// File Name
println("downloadFileName")
println(currentFile.getName())
fileList.add(currentFile.absoluteFile)
}
}
println("fileList")
println(fileList.size)
}
}
}
in the AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.nt_music">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Perhaps I put something wrong in it or something needs to be added
output:
I/System.out: fullpath
/storage/emulated/0/Music
I/System.out: null
But why null!?
maybe it has something to do with these errors that appear in the console
E/libc: Access denied finding property "ro.vendor.df.effect.conflict"
E/Perf: Fail to get file list com.example.nt_music
E/Perf: getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array
Fail to get file list com.example.nt_music
getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array
You can check the required permissions programmatically by calling this method
fun checkPermissions()
{
this.onPermissionGranted = onPermissionGranted
val ungrantedPermissions = requiredPermissionsStillNeeded()
if (ungrantedPermissions.isEmpty()) {
// All permission granted, you can list your files...
} else {
ActivityCompat.requestPermissions(activity, ungrantedPermissions, 1)
}
}
This function calls this method
private fun requiredPermissionsStillNeeded(): Array<String>
{
val permissions = HashSet<String>()
for (permission in getRequiredPermissions()) {
permissions.add(permission)
}
val i = permissions.iterator()
while (i.hasNext()) {
val permission = i.next()
if (ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
i.remove()
}
}
return permissions.toTypedArray()
}
private fun getRequiredPermissions(): Array<String>
{
var permissions: Array<String>? = null
try {
permissions = activity.packageManager.getPackageInfo(activity.packageName, PackageManager.GET_PERMISSIONS).requestedPermissions
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
return permissions?.clone() ?: arrayOf()
}
Lastly, you'll need to override this method in your activity. It is called recursively until your user has granted all the permissions needed by the app.
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
checkPermissions()
}
After that, your app should be able to list the files in the folder.
EDIT
These are all functions that check whether the users have granted you the required permissions.
You should add them to your MainActivity class and edit your onCreate method like this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
println()
//readSongs(dir, ext)
checkPermissions()
}
And then call your list file function in checkPermissions() like this
fun checkPermissions()
{
this.onPermissionGranted = onPermissionGranted
val ungrantedPermissions = requiredPermissionsStillNeeded()
if (ungrantedPermissions.isEmpty()) {
val gpath: String = Environment.getExternalStorageDirectory().absolutePath
val spath = "Music"
val fullpath = File(gpath + File.separator + spath)
println("fullpath")
println(fullpath)
imageReaderNew(fullpath)
} else {
ActivityCompat.requestPermissions(activity, ungrantedPermissions, 1)
}
}
Related
I have created 2 applications: Lets call them A and B. In application A I do have a ContentProvider that I now want to access from application B with a ContentResolver.
However the app craches with the error message:
"E/ActivityThread: Failed to find provider info for com.example.rewardreacher.provider.MyContentProvider"
Here is my application A
DatabaseHandler.kt
package com.example.rewardreacher
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast
import com.example.rewardreacher.provider.MyContentProvider
import com.example.rewardreacher.provider.MyContentProvider.Companion.CREATE_DB_TABLE
import com.example.rewardreacher.provider.MyContentProvider.Companion.DATABASE_NAME
import com.example.rewardreacher.provider.MyContentProvider.Companion.DATABASE_VERSION
import com.example.rewardreacher.provider.MyContentProvider.Companion.TABLE_NAME
class DatabaseHandler
internal constructor(context: Context?) : SQLiteOpenHelper(
context,
DATABASE_NAME,
null,
DATABASE_VERSION
) {
// creating a table in the database
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(CREATE_DB_TABLE)
}
override fun onUpgrade(
db: SQLiteDatabase,
oldVersion: Int,
newVersion: Int
) {
// sql query to drop a table
// having similar name
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
}
MyContentProvider.kt
package com.example.rewardreacher.provider
import android.content.ContentProvider
import android.content.ContentUris
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteQueryBuilder
import android.net.Uri
import android.text.TextUtils
import com.example.rewardreacher.DatabaseHandler
class MyContentProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.rewardreacher.provider.MyContentProvider"
const val URL = "content://$AUTHORITY/goals"
val CONTENT_URI = Uri.parse(URL)
const val id = "id"
const val name = "name"
const val uriCode = 1
var uriMatcher: UriMatcher? = null
private val values: HashMap<String, String>? = null
// declaring name of the database
const val DATABASE_NAME = "GoalDB"
// declaring table name of the database
const val TABLE_NAME = "Goals"
// declaring version of the database
const val DATABASE_VERSION = 1
// sql query to create the table
const val CREATE_DB_TABLE =
(" CREATE TABLE " + TABLE_NAME
+ " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ " name TEXT NOT NULL,"
+ "frequency INTEGER,"
+ "performed INTEGER);")
init {
uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
uriMatcher!!.addURI(
AUTHORITY,
"goals",
uriCode
)
uriMatcher!!.addURI(
AUTHORITY,
"goals/*",
uriCode
)
}
}
override fun getType(uri: Uri): String? {
return when (uriMatcher!!.match(uri)) {
uriCode -> "com.example.rewardreacher.provider.MyContentProvider"
else -> throw IllegalArgumentException("Unsupported URI: $uri")
}
}
override fun onCreate(): Boolean {
val context = context
val dbHelper =
DatabaseHandler(context)
db = dbHelper.writableDatabase
return db != null
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
var sortOrder = sortOrder
val qb = SQLiteQueryBuilder()
qb.tables = TABLE_NAME
when (uriMatcher!!.match(uri)) {
uriCode -> qb.projectionMap = values
else -> throw IllegalArgumentException("Unknown URI $uri")
}
if (sortOrder == null || sortOrder === "") {
sortOrder = id
}
val c = qb.query(
db, projection, selection, selectionArgs, null,
null, sortOrder
)
c.setNotificationUri(context!!.contentResolver, uri)
return c
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val rowID = db!!.insert(TABLE_NAME, "", values)
if (rowID > 0) {
val _uri =
ContentUris.withAppendedId(CONTENT_URI, rowID)
context!!.contentResolver.notifyChange(_uri, null)
return _uri
}
throw SQLiteException("Failed to add a record into $uri")
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
var count = 0
count = when (uriMatcher!!.match(uri)) {
uriCode -> db!!.update(TABLE_NAME, values, selection, selectionArgs)
else -> throw IllegalArgumentException("Unknown URI $uri")
}
context!!.contentResolver.notifyChange(uri, null)
return count
}
override fun delete(
uri: Uri,
selection: String?,
selectionArgs: Array<String>?
): Int {
var count = 0
count = when (uriMatcher!!.match(uri)) {
uriCode -> db!!.delete(TABLE_NAME, selection, selectionArgs)
else -> throw IllegalArgumentException("Unknown URI $uri")
}
context!!.contentResolver.notifyChange(uri, null)
return count
}
private var db: SQLiteDatabase? = null
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.rewardreacher">
<permission android:name="com.provider_with_access_to.READ_GOALS"></permission>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher_reward"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_reward_round"
android:supportsRtl="true"
android:theme="#style/Theme.RewardReacher">
<provider
android:name=".provider.MyContentProvider"
android:authorities="com.example.rewardreacher.provider.MyContentProvider"
android:readPermission="com.provider_with_access_to.READ_GOALS"
android:enabled="true"
android:exported="true">
</provider>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And application B
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.workouttracker">
<uses-permission android:name="com.provider_with_access_to.READ_GOALS" />
<!-- <queries> <package android:name="com.example.rewardreacher.provider.MyContentProvider" /> </queries>-->
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher_workout"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_workout_round"
android:supportsRtl="true"
android:theme="#style/Theme.WorkoutTracker">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity.kt
package com.example.workouttracker
import android.annotation.SuppressLint
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.CheckBox
import android.widget.TextView
import com.google.android.material.textfield.TextInputEditText
class MainActivity : AppCompatActivity() {
var CONTENT_URI = Uri.parse("content://com.example.rewardreacher.provider.MyContentProvider/goals")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
#SuppressLint("Range")
fun onClickShowDetails(view: View?) {
// inserting complete table details in this text field
val resultView = findViewById<View>(R.id.res) as TextView
// creating a cursor object of the
// content URI
val cursor = contentResolver.query(Uri.parse("content://com.example.rewardreacher.provider.MyContentProvider/goals"), null, null, null, null)
// iteration of the cursor
// to print whole table
if (cursor!!.moveToFirst()) {
val strBuild = StringBuilder()
while (!cursor.isAfterLast) {
strBuild.append("""
${cursor.getString(cursor.getColumnIndex("id"))} - ${cursor.getString(cursor.getColumnIndex("name"))} - ${cursor.getString(cursor.getColumnIndex("frequency"))} - ${cursor.getString(cursor.getColumnIndex("performed"))}
""".trimIndent())
cursor.moveToNext()
}
resultView.text = strBuild
} else {
resultView.text = "No Records Found"
}
}
I want to create a Splash screen and show it as long as the authentication state of the user gets determined. I have a global singleton called AuthStateController which holds my state and some extra functions.
But because the installSplashScreen function is outside of a composable I can't use Koin to inject the AuthStateController class to get access to my loading state.
Below is my MainActivity with all my Koin modules. And the installSplashScreen function.
class MainActivity : ComponentActivity() {
// not allowed because outside of Composable
private val authStateController: AuthStateController by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startKoin {
androidLogger(if (BuildConfig.DEBUG) Level.ERROR else Level.NONE)
androidContext(this#MainActivity)
modules(listOf(appModule, networkModule, viewModelModule, interactorsModule))
}
installSplashScreen().apply {
setKeepVisibleCondition {
// need AuthStateController instance to determine loading state
authStateController.state.value.isLoading
}
}
setContent {
M3Theme {
SetupNavGraph()
}
}
}
}
}
This is the Koin module that provides my AuthStateController class:
val appModule = module {
single { AuthStateController(get()) }
}
And this is my AuthStateController class which holds my state and some extra functions:
class AuthStateController(
private val getMeInterceptor: GetMeInterceptor
) {
val state: MutableState<AuthState> = mutableStateOf(AuthState())
fun fetchMe() {
val me = getMeInterceptor.execute().collect(CoroutineScope(Dispatchers.IO)) { dataState ->
dataState.data?.let {
state.value =
state.value.copy(profile = it, isAuthenticated = true, isLoading = false)
}
}
}
init {
val token = settings.getString(Constants.AUTH_TOKEN)
if (token.isNotBlank()) {
fetchMe()
state.value = state.value.copy(authToken = token)
}
}
}
How can I get access to the singleton created with Koin inside the MainActivity and use it inside the installSplashScreen function?
Edit
Android Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.sessions_clean.android">
<uses-permission android:name="android.permission.INTERNET" />
<application
// app crashes when adding line below
android:name=".MainApplication"
android:allowBackup="false"
android:supportsRtl="true"
android:theme="#style/Theme.App.Starting"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="#style/Theme.App.Starting">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
When I add android:name to the already existing application tag the App crashes instantly.
But when I create a new application tag for the new MainApplication I get errors in the IDE like Attribute android:allowBackup is not allowed here
I think you can startKoin inside the Application
MainApplication.kt
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
...
}
}
}
AndroidManifest.xml
<manifest ...>
<application
android:name=".MainApplication"
...>
...
</application>
</manifest>
I try to turn off screen android use deviceManger.lockNow() but it is don't off,this is my code:
DeviceAdmin.kt
package org.deepspeechdemo
import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
class DeviceAdmin : DeviceAdminReceiver(){
override fun onEnabled(context: Context, intent: Intent) {
super.onEnabled(context, intent)
Log.i("DeviceAdmin","Enable")
}
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
Log.i("DeviceAdmin","disable")
}
}
MainActivity
package org.deepspeechdemo
import android.Manifest
import android.app.ActivityManager
import android.content.pm.PackageManager
import android.media.AudioFormat
import android.media.AudioRecord
import android.media.MediaRecorder
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import kotlinx.android.synthetic.main.activity_main.*
import org.deepspeech.libdeepspeech.DeepSpeechModel
import java.io.File
import java.util.concurrent.atomic.AtomicBoolean
import java.util.Arrays
import java.lang.StringBuilder
import android.content.Intent
import android.app.Service
import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.util.Log
class MainActivity : AppCompatActivity() {
private var model: DeepSpeechModel? = null
private var transcriptionThread: Thread? = null
private var isRecording: AtomicBoolean = AtomicBoolean(false)
lateinit var compName: ComponentName
private val TFLITE_MODEL_FILENAME = "deepspeech-0.9.3-models.tflite"
private val SCORER_FILENAME = "deepspeech-0.9.3-models.scorer"
lateinit var deviceManger: DevicePolicyManager
val listOfObjects=arrayOf("just")
private fun checkAudioPermission() {
// Permission is automatically granted on SDK < 23 upon installation.
if (Build.VERSION.SDK_INT >= 23) {
val permission = Manifest.permission.RECORD_AUDIO
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(permission), 3)
}
}
}
private fun transcribe() {
// We read from the recorder in chunks of 2048 shorts. With a model that expects its input
// at 16000Hz, this corresponds to 2048/16000 = 0.128s or 128ms.
val audioBufferSize = 2048
val audioData = ShortArray(audioBufferSize)
deviceManger=getSystemService(DEVICE_POLICY_SERVICE) as DevicePolicyManager
runOnUiThread { btnStartInference.text = "Stop Recording" }
compName= ComponentName(this, DeviceAdmin::class.java)
val enable=deviceManger.isAdminActive(compName)
model?.let { model ->
val streamContext = model.createStream()
val recorder = AudioRecord(
MediaRecorder.AudioSource.VOICE_RECOGNITION,
model.sampleRate(),
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
audioBufferSize
)
recorder.startRecording()
while (isRecording.get()) {
recorder.read(audioData, 0, audioBufferSize)
model.feedAudioContent(streamContext, audioData, audioData.size)
var decoded = model.intermediateDecode(streamContext)
runOnUiThread { transcription.text = decoded }
val array: Array<String> = decoded.split(" ").toTypedArray()
// for (i in array){
// Log.d("aaa",i)}
val found = Arrays.stream(array).anyMatch { t -> t == "just" }
if (found){
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val appTasks = am!!.getAppTasks()
if (appTasks.size > 0) {
val appTask = appTasks.get(0)
appTask.finishAndRemoveTask()
}
}
deviceManger.lockNow()}
}
val decoded = model.finishStream(streamContext)
runOnUiThread {
btnStartInference.text = "Start Recording"
transcription.text = decoded
}
recorder.stop()
recorder.release()
}
}
private fun createModel(): Boolean {
val modelsPath = getExternalFilesDir(null).toString()
val tfliteModelPath = "$modelsPath/$TFLITE_MODEL_FILENAME"
val scorerPath = "$modelsPath/$SCORER_FILENAME"
for (path in listOf(tfliteModelPath, scorerPath)) {
if (!File(path).exists()) {
status.append("Model creation failed: $path does not exist.\n")
return false
}
}
model = DeepSpeechModel(tfliteModelPath)
model?.enableExternalScorer(scorerPath)
return true
}
private fun startListening() {
if (isRecording.compareAndSet(false, true)) {
transcriptionThread = Thread(Runnable { transcribe() }, "Transcription Thread")
transcriptionThread?.start()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
checkAudioPermission()
// Create application data directory on the device
val modelsPath = getExternalFilesDir(null).toString()
status.text = "Ready. Copy model files to \"$modelsPath\" if running for the first time.\n"
}
private fun stopListening() {
isRecording.set(false)
}
fun onRecordClick(v: View?) {
if (model == null) {
if (!createModel()) {
return
}
status.append("Created model.\n")
}
if (isRecording.get()) {
stopListening()
} else {
startListening()
}
}
override fun onDestroy() {
super.onDestroy()
if (model != null) {
model?.freeModel()
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.deepspeechdemo">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:excludeFromRecents="true"
android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".DeviceAdmin"
android:description="#string/app_name"
android:label="#string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
<meta-data
android:name="android.app.device_admin"
android:resource="#xml/policies" />
</receiver>
</application>
</manifest>
policies.xml
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<force-lock/>
</uses-policies>
</device-admin>
After run app , in the specified case if (found)(line 77,MainActivity.kt) app wil stop run but screen don't turn off:
enter image description here
Thank everyone!
I am learning databinding with mvvm but I am getting following errors I did not know what is the main problem.
DataBinderMapperImpl.java:9: error: cannot find symbol
import gahfy.net.databinding.ActivityPostListBindingImpl;
^
symbol: class ActivityPostListBindingImpl
location: package gahfy.net.databinding
error: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding error(s):
[databinding] {"msg":"cannot find method getLoadingVisibility() in class gahfy.net.ui.post.PostListViewModel","file":"C:\\Users\\Edgar\\Documents\\MVVMPosts\\app\\src\\main\\res\\layout\\activity_post_list.xml","pos":[{"line0":22,"col0":37,"line1":22,"col1":68}]}
error: cannot find symbol
import gahfy.net.databinding.ActivityPostListBindingImpl;
^
symbol: class ActivityPostListBindingImpl
location: package gahfy.net.databinding
cannot find method getLoadingVisibility() in class gahfy.net.ui.post.PostListViewModel
what I have tried invalidate cache restart and rebuild and clean project it did not helped at all
below activity_post_list.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="gahfy.net.ui.post.PostListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:mutableVisibility="#{viewModel.getLoadingVisibility()}" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/post_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:adapter="#{viewModel.getPostListAdapter()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
below PostListActivity.kt
import android.os.Bundle
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import gahfy.net.R
import com.google.android.material.snackbar.Snackbar;
import gahfy.net.databinding.ActivityPostListBinding
class PostListActivity: AppCompatActivity() {
private lateinit var binding: ActivityPostListBinding
private lateinit var viewModel: PostListViewModel
private var errorSnackbar: Snackbar? = null
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_post_list)
binding.postList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
viewModel = ViewModelProviders.of(this).get(PostListViewModel::class.java)
viewModel.errorMessage.observe(this, Observer {
errorMessage -> if(errorMessage != null) showError(errorMessage) else hideError()
})
binding.viewModel = viewModel
}
private fun showError(#StringRes errorMessage:Int){
errorSnackbar = Snackbar.make(binding.root, errorMessage, Snackbar.LENGTH_INDEFINITE)
errorSnackbar?.setAction(R.string.retry, viewModel.errorClickListener)
errorSnackbar?.show()
}
private fun hideError(){
errorSnackbar?.dismiss()
}
}
below PostListViewModel.kt
class PostListViewModel:BaseViewModel(){
#Inject
lateinit var postApi: PostApi
private val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
val errorMessage:MutableLiveData<Int> = MutableLiveData()
val errorClickListener = View.OnClickListener { loadPosts() }
private val postListAdapter: PostListAdapter = PostListAdapter()
private lateinit var subscription: Disposable
init{
loadPosts()
}
private fun loadPosts(){
subscription = postApi.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe { onRetrievePostListStart() }
.doOnTerminate { onRetrievePostListFinish() }
.subscribe(
// Add result
{ result -> onRetrievePostListSuccess(result) },
{ onRetrievePostListError() }
)
}
private fun onRetrievePostListStart(){
loadingVisibility.value = View.VISIBLE
errorMessage.value = null
}
private fun onRetrievePostListFinish(){
loadingVisibility.value = View.GONE
}
private fun onRetrievePostListSuccess(postList:List<Post>){
postListAdapter.updatePostList(postList)
}
private fun onRetrievePostListError(){
errorMessage.value = R.string.post_error
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
}
below BindingAdapters.kt
#BindingAdapter("mutableText")
fun setMutableText(view: TextView, text: MutableLiveData<String>?) {
val parentActivity:AppCompatActivity? = view.getParentActivity()
if(parentActivity != null && text != null) {
text.observe(parentActivity, Observer { value -> view.text = value?:""})
}
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Int>?) {
val parentActivity:AppCompatActivity? = view.getParentActivity()
if(parentActivity != null && visibility != null) {
visibility.observe(parentActivity, Observer { value -> view.visibility = value?:View.VISIBLE})
}
}
#BindingAdapter("adapter")
fun setAdapter(view: RecyclerView, adapter: RecyclerView.Adapter<*>) {
view.adapter = adapter
}
}
It's about your databinding usage in xml.
1.Your used variable must be public or a have public getter.
2.If you want use public variable just use it name (without get).
So you must make this changes in this lines.
private val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
private val postListAdapter: PostListAdapter = PostListAdapter()
To
val loadingVisibility: MutableLiveData<Int> = MutableLiveData()
val postListAdapter: PostListAdapter = PostListAdapter()
And
app:mutableVisibility="#{viewModel.getLoadingVisibility()}"
app:adapter="#{viewModel.getPostListAdapter()}"
To
app:mutableVisibility="#{viewModel.loadingVisibility}"
app:adapter="#{viewModel.postListAdapter}"
BindAdapters
class BindAdapters {
companion object {
#BindingAdapter("mutableText")
fun setMutableText(view: TextView, text: MutableLiveData<String>?) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null && text != null) {
text.observe(parentActivity, Observer { value -> view.text = value ?: "" })
}
}
#BindingAdapter("mutableVisibility")
fun setMutableVisibility(view: View, visibility: MutableLiveData<Int>?) {
val parentActivity: AppCompatActivity? = view.getParentActivity()
if (parentActivity != null && visibility != null) {
visibility.observe(
parentActivity,
Observer { value -> view.visibility = value ?: View.VISIBLE })
}
}
#BindingAdapter("adapter")
fun setAdapter(view: RecyclerView, adapter: RecyclerView.Adapter<*>) {
view.adapter = adapter
}
}
}
I am learning kotlin using koin. While running the application in catlog I see the following message.
java.lang.IllegalStateException: KoinApplication has not been started
though I have used startKoin in MyApplication
class MyApplication : Application() {
var listOfModules = module {
single { GitHubServiceApi() }
}
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#MyApplication)
modules(listOfModules)
}
}
}
Adding "android:name=".TheApplication" in the Manifest file solved the issue.
android:name=".TheApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_app_icon_round"
android:supportsRtl="true"
android:theme="#style/Theme.Shrine">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
"android:name=".TheApplication" is the Class Name from Koin
class TheApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
startKoin {
androidLogger()
androidContext(androidContext = this#TheApplication)
modules(
listOfModules
)
}
}
}
In you manifest.xml,
<application>
android:name=".MyApplication"
...
</application
Add this line in application tag.
Basically, you need to give the name of the class where you called startKoin() method, in the Manifest as an application name. And this will let your configure logging, properties loading and modules. Check this out: https://doc.insert-koin.io/#/koin-core/dsl
If you write unit test, you also should add startKoin and inject dependencies:
#Before
fun startKoinForTest() {
startKoin {
modules(DI.modules(...))
}
}
#After
fun stopKoinAfterTest() = stopKoin()
See also Koin Android Test.
private fun getKoin(activity: ComponentActivity): Koin {
return if (activity is KoinComponent) {
activity.getKoin()
} else {
GlobalContext.getOrNull() ?: startKoin {
androidContext(activity)
modules(listModule)
}.koin
}
}
fun ComponentActivity.contextAwareActivityScope() = runCatching {
LifecycleScopeDelegate<Activity>(
lifecycleOwner = this,
koin = getKoin(this)
)}.getOrElse { activityScope()
}