After creating an activity for a Settings page I noticed that, mCurrentValue !== value in if (mCurrentValue !== value) returned a warning:
Identity equality for arguments of types Boolean? and Boolean can be unstable because of implicit boxing
I tried resolving this warning by adding a ? next to Boolean in override fun onXchange(value:Boolean) {, but then the following error returned:
'onXchange' overrides nothing
Any ideas on how to resolve this issue?
Activity class
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
class MySettingsActivity : AppCompatActivity(), MySettingsFragment.PreferenceXchangeListener {
private var mCurrentValue: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
val mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mCurrentValue = mSharedPreferences.getBoolean("preference_a", false)
if (mCurrentValue as Boolean)
{
setTheme(R.style.MyDarkAppCompatTheme)
}
else
{
setTheme(R.style.MyLightAppCompatTheme)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mysettings)
val settingsFragment = MySettingsFragment()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, settingsFragment)
.commit()
val myActionBar = actionBar
if (myActionBar != null)
{
myActionBar.setTitle(R.string.settings)
myActionBar.setBackgroundDrawable(ColorDrawable(Color.BLACK))
myActionBar.setDisplayHomeAsUpEnabled(true)
myActionBar.setDisplayShowHomeEnabled(true)
myActionBar.setHomeAsUpIndicator(resources.getDrawable(R.drawable.ic_arrow_back_white, null))
}
}
override fun onXchange(value:Boolean?) {
if (mCurrentValue !== value) {
mCurrentValue = value
recreate()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
val intent = parentActivityIntent
intent?.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
onBackPressed()
return true
}
else ->
return super.onOptionsItemSelected(item)
}
}
}
Fragment class
class MySettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_preferences)
val mCheckBoxPreference = findPreference("preference_a") as CheckBoxPreference
mCheckBoxPreference.onPreferenceChangeListener = this
}
private var mPreferenceXchangeListener: PreferenceXchangeListener? = null
interface PreferenceXchangeListener {
fun onXchange(value:Boolean)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try
{
mPreferenceXchangeListener = context as MySettingsFragment.PreferenceXchangeListener
}
catch (e:ClassCastException) {
Log.e(TAG, "onAttach::::: PreferenceXchangeListener must be set in parent Activity")
}
}
override fun onPreferenceChange(preference: Preference, newValue:Any):Boolean {
val preferenceKey = preference.key
if (preferenceKey == "preference_a")
{
(preference as CheckBoxPreference).isChecked = newValue as Boolean
mPreferenceXchangeListener!!.onXchange(newValue)
return true
}
return false
}
companion object {
private val TAG = MySettingsFragment::class.java.simpleName
}
}
So first I can explain the issue it describes. Let's propose a very contrived scenario:
BooleanProvider.java
class BooleanProvider {
#NonNull Boolean wrapMyBoolean(boolean state) {
return new Boolean(state);
}
}
BooleanProviderTest.kt
class BooleanProviderTest {
#Test fun `it returns a true value when true is provided`() {
assertSame(BooleanProvider().wrapMyBoolean(true), true)
}
}
This test will actually fail, because the instances of Boolean are not the same. In the Java code, we initialized a new instance of Boolean (rather than the statically defined Boolean.TRUE and Boolean.FALSE instances that you'll get when a primitive is auto-boxed to a java.lang.Boolean). So to avoid a potential unexpected result, it is recommending that you don't compare these types by reference.
The simplest fix would be to just change your equality test to != instead of !==. This would perform identity equality testing rather than reference equality testing, and will handle a null value on either side appropriately. This is what you want 99% of the time anyway.
Secondly, you can also just declare mCurrentValue as a non-null type, and give it a default value, if you don't need to handle a null value in a specific way. Just declare it as:
private var mCurrentValue: Boolean = false // false is the default here
Related
I am trying to save a name in SharedPreferences. The app is set up so that it recognizes devices by their MAC address. There's an EditText view that populates with the advertised name of the BLE peripheral, and then the user can alter the name inside of the EditText. Once the EditText loses focus, the Data class is supposed to save the user input in SharedPreferences, where the address of the device is the key and the name of the device is the value. Instead, when the EditText loses focus, the name just reverts to the original name found in the BLE advertisement.
I have already looked for answers from these resources (1, 2, 3, 4, 5).
As far as I can tell, I have everything set up correctly in my application, but I am not getting the results I'm looking for. I have three main classes: MainActivity, ScanResultAdapter, and Data. MainActivity is the driver of the program, ScanResultAdapter manages the incoming BLE advertisements from peripheral devices, and Data is a singleton class that manages information for the entire application.
This is how I have it set up in my application:
MainActivity
...
// declaration of the myPrefs variable
lateinit var myPrefs: SharedPreferences
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// instantiation of the myPrefs variable
myPrefs = PreferenceManager.getDefaultSharedPreferences(this)
// passing the context to the data-tracking Data class so that it can access MainActivity's SharedPreferences
data = Data.getInstance(this)
mainBinding.saveNamesButton.onClick {
mainBinding.saveNamesButton.requestFocusFromTouch()
}
rowScanResultBinding.deviceName.onFocusChangeListener = OnFocusChangeListener { v, hasFocus ->
if (!hasFocus) {
val name = rowScanResultBinding.deviceName.text.toString()
val address = rowScanResultBinding.macAddress.text.toString()
if (data.sharedPref.contains(address) && rowScanResultBinding.deviceName.text.toString() != data.sharedPref.getString(address, null)) {
rowScanResultBinding.deviceName.setText(data.sharedPref.getString(address, null))
}
}
}
}
...
override fun onPause() {
super.onPause()
// TODO: Fix this line to resolve an initialization error
data.saveSharedPreference()
}
ScanResultAdapter
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (items[position].device.name == "EES-TEMP-1") {
val item = items[position]
if (!data.names.contains(item.device.address)) {
data.names[item.device.address] = item.device.name
data.sharedPref.edit().putString(item.device.address, item.device.name).apply()
}
println(data.names)
holder.bind(item)
} else return
}
...
inner class ViewHolder(
private val view: View,
private val onClickListener: ((device: ScanResult) -> Unit)
) : RecyclerView.ViewHolder(view) {
...
with(view) {
if (data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(data.getName(result.device.address))
} else if (!data.hasName(result.device.address) && !device_name.hasFocus()) {
device_name.setText(result.device.address)
}
}
Data
class Data(private val context: Context) {
companion object {
private var instance: Data? = null
fun getInstance(context: Context): Data {
return instance ?: synchronized(this) {
instance ?: Data(context.applicationContext).also { instance = it }
}
}
}
val names: MutableMap<String, String> = mutableMapOf()
val sharedPref: SharedPreferences = context.getSharedPreferences("MySharedPreferencesFile", Context.MODE_PRIVATE)
val addresses = mutableSetOf<String>()
fun saveSharedPreference() {
sharedPref.edit {
for ((key, value) in names) {
if (!sharedPref.contains(key)) {
putString(key, value)
apply()
}
}
}
}
fun getName(address: String): String? {
if (sharedPref.contains(address)) {
return sharedPref.getString(address, "Unnamed")
}
return "failed"
}
fun hasName(key: String): Boolean {
return sharedPref.contains(key)
}
fun addName(key: String, value: String) {
names[key] = value
}
fun addAddress(address: String) {
addresses.add(address)
}
fun addItem(address: String, name: String) {
sharedPref.edit().putString(address, name).apply()
}
}
For an app I am making I have a list in which I display pixel art creations, I do this with a RecyclerView and DiffUtil, here is the code:
package com.therealbluepandabear.pixapencil.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.therealbluepandabear.pixapencil.R
import com.therealbluepandabear.pixapencil.databinding.RecentCreationsLayoutBinding
import com.therealbluepandabear.pixapencil.enums.SnackbarDuration
import com.therealbluepandabear.pixapencil.extensions.setOnLongPressListener
import com.therealbluepandabear.pixapencil.extensions.showSnackbar
import com.therealbluepandabear.pixapencil.listeners.RecentCreationsListener
import com.therealbluepandabear.pixapencil.models.PixelArt
import com.therealbluepandabear.pixapencil.viewholders.PixelArtViewHolder
class PixelArtAdapter(
private val snackbarView: View,
private val listener: RecentCreationsListener,
private val context: Context
) : ListAdapter<PixelArt, RecyclerView.ViewHolder>(diffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val binding = RecentCreationsLayoutBinding.inflate(LayoutInflater.from(parent.context))
return PixelArtViewHolder(binding, context)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val pixelArt = getItem(position)
if (holder is PixelArtViewHolder) {
holder.bind(pixelArt)
holder.binding.recentCreationsLayoutMaterialCardView.setOnClickListener {
listener.onCreationTapped(pixelArt)
}
holder.binding.recentCreationsLayoutMaterialCardView.setOnLongPressListener {
listener.onCreationLongTapped(pixelArt)
}
holder.binding.recentCreationsLayoutFavoriteButton.setOnClickListener {
if (pixelArt.starred) {
pixelArt.starred = false
listener.onUnstarredTapped(pixelArt)
unFavouriteRecentCreation(snackbarView, pixelArt)
holder.bind(pixelArt)
} else {
pixelArt.starred = true
listener.onStarredTapped(pixelArt)
favouriteRecentCreation(snackbarView, pixelArt)
holder.bind(pixelArt)
}
}
}
}
private fun favouriteRecentCreation(contextView: View, pixelArt: PixelArt) { // move to listener
contextView.showSnackbar(contextView.context.getString(R.string.snackbar_pixel_art_project_saved_to_starred_items_in_code_str, pixelArt.title), SnackbarDuration.Default)
pixelArt.starred = true
}
private fun unFavouriteRecentCreation(contextView: View, pixelArt: PixelArt) {
contextView.showSnackbar(contextView.context.getString(R.string.snackbar_pixel_art_project_removed_from_starred_items_in_code_str, pixelArt.title), SnackbarDuration.Default)
pixelArt.starred = false
}
companion object {
val diffCallback: DiffUtil.ItemCallback<PixelArt> = object : DiffUtil.ItemCallback<PixelArt>() {
override fun areItemsTheSame(oldItem: PixelArt, newItem: PixelArt): Boolean {
return oldItem.objId == newItem.objId
}
override fun areContentsTheSame(oldItem: PixelArt, newItem: PixelArt): Boolean {
return oldItem == newItem
}
}
}
}
ViewHolder:
class PixelArtViewHolder(val binding: RecentCreationsLayoutBinding, private val context: Context) : RecyclerView.ViewHolder(binding.root) {
private fun loadPixelArtCoverImage(pixelArt: PixelArt) {
val widthHeight = if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
350
} else {
750
}
val requestOptions: RequestOptions = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.priority(Priority.IMMEDIATE)
.encodeFormat(Bitmap.CompressFormat.PNG)
.override(widthHeight, widthHeight)
.centerInside()
.format(DecodeFormat.DEFAULT)
Glide.with(itemView.context)
.setDefaultRequestOptions(requestOptions)
.load(File(itemView.context.getFileStreamPath(pixelArt.coverBitmapFilePath).absolutePath))
.transition(DrawableTransitionOptions.withCrossFade())
.placeholder(R.drawable.transparent_placeholder)
.into(binding.recentCreationsLayoutImageView)
}
private fun loadPixelArtTitle(pixelArt: PixelArt) {
if (pixelArt.title.length > 6) {
binding.recentCreationsLayoutTitle.ellipsize = TextUtils.TruncateAt.MARQUEE
binding.recentCreationsLayoutTitle.isSelected = true
binding.recentCreationsLayoutTitle.isSingleLine = true
(pixelArt.title + " ".repeat(10)).repeat(200).also { binding.recentCreationsLayoutTitle.text = it }
} else {
binding.recentCreationsLayoutTitle.text = pixelArt.title
}
}
private fun loadPixelArtStarred(pixelArt: PixelArt) {
binding.recentCreationsLayoutFavoriteButton.setImageResource(
if (pixelArt.starred) {
R.drawable.ic_baseline_star_24
} else {
R.drawable.ic_baseline_star_border_24
}
)
}
fun bind(pixelArt: PixelArt){
loadPixelArtCoverImage(pixelArt)
binding.recentCreationsLayoutSubtitle.text = context.getString(R.string.recentCreationsLayoutSubtitle_str, pixelArt.width, pixelArt.height)
loadPixelArtStarred(pixelArt)
loadPixelArtTitle(pixelArt)
}
}
Here is the result:
When the user long taps on a project, they get the following dialog:
When they press 'Rename', they get the following dialog where they can rename the project:
My issue is, that when the user types in a new name, and then presses OK, the data is not updating. Sometimes it takes twice to update, sometimes I need to restart the app for it to update, and sometimes it doesn't update at all.
Here is the code responsible for renaming:
fun MainActivity.extendedOnRenameTapped(pixelArt: PixelArt, bottomSheetDialog: BottomSheetDialog) {
val inflatedActivity = activity()?.layoutInflater?.inflate(R.layout.save_file_under_new_name_alert, activity()?.findViewById(android.R.id.content),false)
val textInput: TextInputLayout = inflatedActivity as TextInputLayout
showDialog(
getString(R.string.dialog_rename_title_in_code_str),
null,
getString(R.string.generic_ok_in_code_str), { _, _ ->
val input: String = textInput.editText?.text.toString()
if (input.isNotBlank()) {
pixelArt.title = input
pixelArtViewModel.update(pixelArt)
adapter.submitList(pixelArtData)
bottomSheetDialog.dismiss()
}
}, getString(R.string.generic_cancel_in_code_str), null, view = textInput, dimBackground = false
)
}
I am following everything by the book, so I am confused why this is not working.
Edit
I tried to make it all 'val' and then add this:
pixelArtViewModel.update(pixelArt.copy(title = input))
pixelArtViewModel.getAll().observe(this) {
adapter.submitList(it)
}
bottomSheetDialog.dismiss()
Still not working.
I see that you are setting pixelArt.title, which means your PixelArt class is mutable (has var properties or val properties that reference mutable classes). DiffUtil is 100% incompatible with mutable classes, because they make it impossible to compare items in the old and new lists. It will see the old list as having the new value already so it will treat it as unchanged.
Example with my imagined version of your PixelArt class.
data class PixelArt(
val objId: Long,
val name: String,
val starred: Boolean,
val imageFilePath: String
)
// In ViewModel:
// You probably have the list backed up to disk somehow. I'm just using
// placeholder functions to represent working with the repo or files or
// whatever you use.
val pixelArtLiveData = MutableLiveData<List<PixelArt>>().also {
viewModelScope.launch { it.value = readThePersistedData() }
}
private fun modifyItem(oldItem: PixelArt, newItem: PixelArt) {
pixelArtLiveData.value = pixelArtLiveData.value.orEmpty()
.map { if (it == oldItem) newItem else it }
// also update your persisted data here
}
fun renameItem(originalItem: PixelArt, newName: String) {
modifyItem(originalItem, originalItem.copy(name = newName))
}
fun toggleItemStarred(originalItem: PixelArt) {
modifyItem(originalItem, originalItem.copy(starred = !originalItem.starred))
}
// etc. or you could just make modifyItem public instead of making
// all these helper functions
Then in your adapter, you must call through to these ViewModel functions instead of directly modifying the items or the list or calling submitList. Since the adapter doesn't have direct access to the ViewModel, you probably use your RecentCreationsListener for this by adding
appropriate actions to it that your various click listeners can call.
Your Activity or Fragment would observe this LiveData and simply call submitList() with the observed value.
Could anyone help me to tell how can I disable the firebase logging in Google ML Kit library for android. For every 15 mins it will POST some information to https://firebaselogging.googleapis.com/v0cc/log/batch?format=json_proto3
I tried using the recommendation from google https://firebase.google.com/docs/perf-mon/disable-sdk?platform=android#kotlin+ktx and I am also not sure whether it is a right way.
Suggestions are welcome.
Google's guide from the question didn't work for me, so I have looked for alternatives.
The library is obfuscated, so it is hard to be sure, but it appears that the logging is hardcoded in. However, there is a very hacky way to disable it through some fragile reflection:
import android.util.Log
import com.google.mlkit.common.sdkinternal.LazyInstanceMap
import java.lang.reflect.Field
/**
* This class tries to disable MLKit's phoning home/logging.
* This is extremely hacky and will probably break in the next update (obfuscated class names will probably need renaming).
*
* This class exploits the fact, that there are multiple options classes which control this
* (look for "MLKitLoggingOptions" in toString implementation) and for some reason MLKit uses them as keys
* in LazyInstanceMaps which exist as static (usually) variables (which are themselves lazy).
*
* This makes sure that the LazyInstanceMaps exist, then it hijacks their internal HashMap implementation
* and replaces it with a custom map, that creates instances of whatever with logging disabled.
*
* The way to detect which holder classes need renaming, look at the stack trace, for example:
* ```
java.lang.NoClassDefFoundError: Failed resolution of: Lcom/google/android/datatransport/cct/CCTDestination;
at com.google.android.gms.internal.mlkit_vision_barcode.zznu.<init>(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:1)
at com.google.android.gms.internal.mlkit_vision_barcode.zznf.<init>(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:3)
at com.google.android.gms.internal.mlkit_vision_barcode.zznw.create(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:4)
at com.google.mlkit.common.sdkinternal.LazyInstanceMap.get(com.google.mlkit:common##18.0.0:3)
at com.google.android.gms.internal.mlkit_vision_barcode.zznx.zza(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:2)
at com.google.android.gms.internal.mlkit_vision_barcode.zznx.zzb(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:3)
at com.google.mlkit.vision.barcode.internal.zzf.create(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:3)
at com.google.mlkit.common.sdkinternal.LazyInstanceMap.get(com.google.mlkit:common##18.0.0:3)
at com.google.mlkit.vision.barcode.internal.zze.zzb(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:2)
at com.google.mlkit.vision.barcode.BarcodeScanning.getClient(com.google.android.gms:play-services-mlkit-barcode-scanning##18.0.0:3)
* ```
* here are two LazyInstanceMap lookups, of which only the second one (through trial and error or with debugger)
* uses MLKitLoggingOptions keys. From here we can find that the holder class is com.google.android.gms.internal.mlkit_vision_barcode.zznx .
*/
object MLKitTrickery {
private class mlkit_vision_barcodeLoggingOptions(base: com.google.android.gms.internal.mlkit_vision_barcode.zzne) : com.google.android.gms.internal.mlkit_vision_barcode.zzne() {
private val libraryName: String = base.zzb()
private val firelogEventType: Int = base.zza()
override fun zza(): Int = firelogEventType
override fun zzb(): String = libraryName
override fun zzc(): Boolean = false //enableFirelog
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as mlkit_vision_barcodeLoggingOptions
if (libraryName != other.libraryName) return false
if (firelogEventType != other.firelogEventType) return false
return true
}
override fun hashCode(): Int {
var result = libraryName.hashCode()
result = 31 * result + firelogEventType
return result
}
}
private class mlkit_vision_commonLoggingOptions(base: com.google.android.gms.internal.mlkit_vision_common.zzjn) : com.google.android.gms.internal.mlkit_vision_common.zzjn() {
private val libraryName: String = base.zzb()
private val firelogEventType: Int = base.zza()
override fun zza(): Int = firelogEventType
override fun zzb(): String = libraryName
override fun zzc(): Boolean = false //enableFirelog
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as mlkit_vision_commonLoggingOptions
if (libraryName != other.libraryName) return false
if (firelogEventType != other.firelogEventType) return false
return true
}
override fun hashCode(): Int {
var result = libraryName.hashCode()
result = 31 * result + firelogEventType
return result
}
}
private fun isMLKitLoggingOptions(obj: Any): Boolean {
return obj is com.google.android.gms.internal.mlkit_vision_barcode.zzne
|| obj is com.google.android.gms.internal.mlkit_vision_common.zzjn
}
private fun convertMLKitLoggingOptions(obj: Any): Any? {
if (obj is com.google.android.gms.internal.mlkit_vision_barcode.zzne) {
return mlkit_vision_barcodeLoggingOptions(obj)
}
if (obj is com.google.android.gms.internal.mlkit_vision_common.zzjn) {
return mlkit_vision_commonLoggingOptions(obj)
}
return null
}
#Suppress("UNCHECKED_CAST")
private fun patchLazyMap(lazyMapHolder:Any?, lazyMapHolderClass: Class<*>) {
val holderField = lazyMapHolderClass.declaredFields.find { LazyInstanceMap::class.java.isAssignableFrom(it.type) }!!
var currentLazyInstanceMap = holderField.get(lazyMapHolder)
if (currentLazyInstanceMap == null) {
var lastError: Throwable? = null
for (constructor in holderField.type.declaredConstructors) {
try {
constructor.isAccessible = true
val params = arrayOfNulls<Any?>(constructor.parameterCount)
currentLazyInstanceMap = constructor.newInstance(*params)
holderField.set(lazyMapHolder, currentLazyInstanceMap)
} catch (e:Throwable) {
lastError = e
}
}
if (currentLazyInstanceMap == null) {
throw java.lang.Exception("Failed to initialize LazyInstanceMap "+holderField.type, lastError)
}
}
var mapHolderClass: Class<*> = currentLazyInstanceMap.javaClass
val createMethod = mapHolderClass.getDeclaredMethod("create", Object::class.java)
val mapField: Field
while (true) {
val mapFieldCandidate = mapHolderClass.declaredFields.firstOrNull { Map::class.java.isAssignableFrom(it.type) }
if (mapFieldCandidate != null) {
mapField = mapFieldCandidate
break
}
mapHolderClass = mapHolderClass.superclass ?: error("It appears that ${currentLazyInstanceMap.javaClass} does not have a backing map field")
}
val oldMap = mapField.get(currentLazyInstanceMap) as MutableMap<Any, Any?>
val customMap = object : MutableMap<Any, Any?> by oldMap {
override fun containsKey(key: Any): Boolean {
if (oldMap.containsKey(key)) {
return true
}
if (isMLKitLoggingOptions(key)) {
return true
}
return false
}
override fun get(key: Any): Any? {
val existing = oldMap.get(key)
if (existing != null) {
return existing
}
val convertedKey = convertMLKitLoggingOptions(key)
if (convertedKey != null) {
val created = createMethod.invoke(currentLazyInstanceMap, convertedKey)
oldMap.put(key, created)
return created
}
return null
}
}
mapField.isAccessible = true
mapField.set(currentLazyInstanceMap, customMap)
}
private var initialized = false
/**
* Call this to attempt to disable MLKit logging.
*/
fun init() {
try {
patchLazyMap(null, com.google.android.gms.internal.mlkit_vision_barcode.zznx::class.java)
patchLazyMap(null, com.google.android.gms.internal.mlkit_vision_common.zzkc::class.java)
initialized = true
} catch (e: Throwable) {
Log.e("MLKitTrickery", "Failed to disable MLKit phoning home")
}
}
}
When you also shim out GMS TelemetryLogging with:
#file:Suppress("unused", "UNUSED_PARAMETER")
package com.google.android.gms.common.internal
import android.app.Activity
import android.content.Context
import android.os.Parcel
import com.google.android.gms.tasks.OnFailureListener
import com.google.android.gms.tasks.OnSuccessListener
import com.google.android.gms.tasks.Task
import java.util.concurrent.Executor
class TelemetryLoggingOptions {
class Builder {
fun setApi(api: String?): Builder = this
fun build(): TelemetryLoggingOptions = TelemetryLoggingOptions()
}
companion object {
#JvmStatic
fun builder(): Builder = Builder()
}
}
private object DummyLogTask : Task<Void?>() {
override fun addOnFailureListener(p0: OnFailureListener): Task<Void?> {
// Implemented, because failing tells MLKit to back-off for 30 minutes, which is a win for performance
p0.onFailure(exception)
return this
}
override fun addOnFailureListener(p0: Activity, p1: OnFailureListener): Task<Void?> = addOnFailureListener(p1)
override fun addOnFailureListener(p0: Executor, p1: OnFailureListener): Task<Void?> = addOnFailureListener(p1)
override fun addOnSuccessListener(p0: OnSuccessListener<in Void?>): Task<Void?> = this
override fun addOnSuccessListener(p0: Activity, p1: OnSuccessListener<in Void?>): Task<Void?> = addOnSuccessListener(p1)
override fun addOnSuccessListener(p0: Executor, p1: OnSuccessListener<in Void?>): Task<Void?> = addOnSuccessListener(p1)
override fun getException(): Exception? = exception
override fun getResult(): Void? = null
override fun <X : Throwable?> getResult(p0: Class<X>): Void? = null
override fun isCanceled(): Boolean = false
override fun isComplete(): Boolean = true
override fun isSuccessful(): Boolean = false
private val exception = Exception("Success was never an option")
}
object TelemetryLogging {
#JvmStatic
fun getClient(context: Context): TelemetryLoggingClient {
return object : TelemetryLoggingClient {
override fun log(data: TelemetryData): Task<Void?> {
return DummyLogTask
}
}
}
#JvmStatic
fun getClient(context: Context, options: TelemetryLoggingOptions): TelemetryLoggingClient {
return getClient(context)
}
}
interface TelemetryLoggingClient {
fun log(data: TelemetryData): Task<Void?>
}
class TelemetryData(var1: Int, var2:List<MethodInvocation>?) {
fun writeToParcel(var1: Parcel, var2: Int) {}
}
class MethodInvocation {
constructor(methodKey:Int, resultStatusCode:Int, connectionResultStatusCode:Int,
startTimeMillis:Long, endTimeMillis:Long,
callingModuleId: String?, callingEntryPoint: String?, serviceId:Int)
constructor(methodKey:Int, resultStatusCode:Int, connectionResultStatusCode:Int,
startTimeMillis:Long, endTimeMillis:Long,
callingModuleId: String?, callingEntryPoint: String?,
serviceId:Int, var11:Int)
fun writeToParcel(var1: Parcel, var2: Int) {}
}
it is possible to trim many transitive dependencies and save apk size:
implementation("com.google.mlkit:barcode-scanning:17.0.2") {
exclude("com.google.android.gms", "play-services-base")
exclude("com.google.android.datatransport", "transport-api")
exclude("com.google.android.datatransport", "transport-backend-cct")
exclude("com.google.android.datatransport", "transport-runtime")
exclude("com.google.firebase", "firebase-encoders-json")
exclude("com.google.firebase", "firebase-encoders")
}
However, as noted above, this is very fragile and will probably somehow break after MLKit update. It would be nice if this was not needed.
I am new to kotlin and i am trying to pass a value of checked radio button from one class to another activity through interface. I have an interface named RadioGroupHelperInterface as
interface RadioGroupHelperInterface {
fun onSelect(selectedItem: String)
}
Then i have a class from where i want to pass the value of checked radio button.
class GRadioGroupHelper {
private val radioGroupHelperInterface: RadioGroupHelperInterface? = null
fun setRadioExclusiveClick(parent: ViewGroup?) {
val radios: List<RadioButton>? = parent?.let { getRadioButtons(it) }
if (radios != null) {
for (radio in radios) {
radio.setOnClickListener { v ->
val r: RadioButton = v as RadioButton
r.isChecked = true
radioGroupHelperInterface?.onSelect(r.text as String)
checkedValue = r.text as String
for (r2 in radios) {
if (r2.getId() !== r.getId()) {
r2.isChecked = false
}
}
}
}
}
}
}
Finally my activity is as follows:
class ChooseCategoryActivity : AppCompatActivity(), View.OnClickListener,RadioGroupHelperInterface {
var radioGRadioGroupHelper=GRadioGroupHelper()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_choose_category)
setListener()
val parent: ViewGroup = findViewById(R.id.svCategories)
radioGRadioGroupHelper.setRadioExclusiveClick(parent)
}
override fun onSelect(selectedItem: String) {
Log.e("Here","reached")
Log.e("value",selectedItem)
Toast.makeText(this,selectedItem,Toast.LENGTH_LONG).show()
}
}
But i am not able to get the value that i have checked in the radio box from the activity though the value can be printed in the RadioGRadioGroupHelper class. Can anybody please help me?
You don't set radioGroupHelpedInterface field to any value except for null, which is its initial state. Why don't you try this:
Declare your GRadioGroupHelper as following:
class GRadioGroupHelper (private val helperInterface: RadioGroupHelperInterface) {
// All your logic remains the same
}
This will allow you to avoid nullability of the RadioGroupHelperInterface instance and you will also be able to set it via constructor like this in the activity:
val radioGRadioGroupHelper = GRadioGroupHelper(this)
Note that I changed var to val as we don't expect your radioGRadioGroupHelper to change.
After declaring a CheckboxPreference in my activity for my app's settings, a warning appears for as in the line 'val mCheckBoxPreference = findPreference("preference_a") as CheckBoxPreference'. What should be done so that the cast does succeed?
This cast can never succeed
app_preferences.xml
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:key="preference_a"
android:defaultValue="false"
android:title="Preference A"/>
</PreferenceScreen>
fragment class
import android.content.Context
import android.os.Bundle
import android.preference.CheckBoxPreference
import android.preference.Preference
import android.support.v7.preference.PreferenceFragmentCompat
import android.util.Log
class MySettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_preferences)
val mCheckBoxPreference = findPreference("preference_a") as CheckBoxPreference
mCheckBoxPreference.onPreferenceChangeListener = this
}
// declaring PreferenceXchangeListener
private var mPreferenceXchangeListener: PreferenceXchangeListener? = null
// declaring PreferenceXchangeListener in order to communicate with Activities
interface PreferenceXchangeListener {
fun onXchange(value:Boolean)
}
override fun onAttach(context: Context) {
super.onAttach(context)
// on attach - assign parent Activity as PreferenceXchangeListener
try
{
mPreferenceXchangeListener = context as MySettingsFragment.PreferenceXchangeListener
}
catch (e:ClassCastException) {
Log.e(TAG, "onAttach::::: PreferenceXchangeListener must be set in parent Activity")
}
}
override fun onPreferenceChange(preference: Preference, newValue:Any):Boolean {
val preferenceKey = preference.key
if (preferenceKey == "preference_a")
{
(preference as CheckBoxPreference).isChecked = newValue as Boolean
// executing parent Activity's callback with the new value
mPreferenceXchangeListener!!.onXchange(newValue)
return true
}
return false
}
companion object {
private val TAG = MySettingsFragment::class.java.simpleName
}
}
activity class
class MySettingsActivity : AppCompatActivity(), MySettingsFragment.PreferenceXchangeListener {
private var mCurrentValue: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
val mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mCurrentValue = mSharedPreferences.getBoolean("preference_a", false)
if (mCurrentValue as Boolean)
{
setTheme(R.style.MyDarkAppCompatTheme)
}
else
{
setTheme(R.style.MyLightAppCompatTheme)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_mysettings)
val settingsFragment = MySettingsFragment()
supportFragmentManager
.beginTransaction()
.replace(R.id.settings_container, settingsFragment)
.commit()
val myActionBar = actionBar
if (myActionBar != null)
{
myActionBar.setTitle(R.string.settings)
myActionBar.setBackgroundDrawable(ColorDrawable(Color.BLACK))
}
}
override fun onXchange(value:Boolean) {
if (value !== mCurrentValue)
{
mCurrentValue = value
recreate()
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
val intent = parentActivityIntent
intent?.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
onBackPressed()
return true
}
else ->
return super.onOptionsItemSelected(item)
}
}
companion object {
private val TAG = MySettingsActivity::class.java.simpleName
}
}
TL;DR You have to change the import to android.support.v7.preference.CheckBoxPreference.
There are two versions of CheckBoxPreference:
android.preference.CheckBoxPreference, which was added in API level 1 and has android.preference.Preference as ancestor class
android.support.v7.preference.CheckBoxPreference which belongs to the support library and has android.support.v7.preference.Preference as ancestor class
Your Fragment extends from PreferenceFragmentCompat, so findPreference() will return a android.support.v7.preference.Preference. Since you can't cast support Preference classes to their non support equivalents, Android Studio is showing the error message "This cast can never succeed" when you attempt to cast to the non support CheckBoxPreference.