I am using glide and once the image is retrieved it is put into a view, the view is extended and I use the below code in the constructor. The code returns null. Is there a way to wait until it returns the drawable? It works in the listeners and that's why I am asking if there's a way to wait so it can be in the constructors.
Code:
Glide:
Glide
.with(activity).asBitmap()
.load(imageURL)
.into(new SimpleTarget<Bitmap>(currentMap.getWidth(), currentMap.getHeight()) {
#Override
public void onResourceReady(#NonNull Bitmap resource, #Nullable Transition<? super Bitmap> transition) {
currentMap.setUpMap()
});
}
Drawable in view:
drawable = this.getDrawable();
Error:
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.drawable.Drawable.getIntrinsicWidth()' on a null object reference
Use a RequestListener<String, GlideDrawable> that gives you a drawable:
(GlideDrawable is also a Drawable).
Glide.with(activity)
.load("...")
.listener(object : RequestListener<String, GlideDrawable> {
override fun onResourceReady(resource: GlideDrawable?, model: String?, target: Target<GlideDrawable>?, isFromMemoryCache: Boolean, isFirstResource: Boolean): Boolean {
// GlideDrawable extends Drawable :)
}
override fun onException(e: Exception?, model: String?, target: Target<GlideDrawable>?, isFirstResource: Boolean): Boolean = false
})
You will need to remove the asBitmap() call.
My code is kotlin because I copy-pasted it from my project, but I believe you can understand it.
Related
I am using this library to put a carousel view in an Android app: https://github.com/ImaginativeShohag/Why-Not-Image-Carousel
I'm also trying to use the showcase type, but a prerequisite to use this type is creating a custom layout for the carousel items.
Creating the layout I understand, but the OP uses this example in Kotlin to show how the custom layout is actually used:
binding.carousel3.carouselListener = object : CarouselListener {
override fun onCreateViewHolder(
layoutInflater: LayoutInflater,
parent: ViewGroup
): ViewBinding? {
return ItemCustomFixedSizeLayout1Binding.inflate(layoutInflater, parent, false)
}
override fun onBindViewHolder(
binding: ViewBinding,
item: CarouselItem,
position: Int
) {
val currentBinding = binding as ItemCustomFixedSizeLayout1Binding
currentBinding.imageView.apply {
scaleType = imageScaleType
// carousel_default_placeholder is the default placeholder comes with
// the library.
setImage(item, R.drawable.carousel_default_placeholder)
}
}
}
val listThree = mutableListOf<CarouselItem>()
for (item in DataSet.three) {
listThree.add(
CarouselItem(
imageUrl = item.first,
caption = item.second
)
)
}
binding.carousel3.setData(listThree)
binding.customCaption.isSelected = true
binding.carousel3.onScrollListener = object : CarouselOnScrollListener {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int,
position: Int,
carouselItem: CarouselItem?
) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
carouselItem?.apply {
binding.customCaption.text = caption
}
}
}
override fun onScrolled(
recyclerView: RecyclerView,
dx: Int,
dy: Int,
position: Int,
carouselItem: CarouselItem?
) {
// ...
}
}
// Custom navigation
binding.btnGotoPrevious.setOnClickListener {
binding.carousel3.previous()
}
binding.btnGotoNext.setOnClickListener {
binding.carousel3.next()
}
I'm having some trouble figuring out what exactly this code is doing and how it would look in Java. Any pointers would be greatly appreciated!
A Quick Guess
It seems that the listener is providing the callback of Recycler View. If you need me to guess within a second, and I will say the custom view is a Recycler View using listener to allow users to register the Recycler View methods (which is the Adapter using in the RV)
Deep Investigation
1st question: What is the Custom View class for id=carousel3 in KotlinActivity in the sample project
Ans: org.imaginativeworld.whynotimagecarousel.ImageCarousel.
(P.S. identical between activity_kotlin.xml and activity_test.xml)
(Below is a screen cap, don't try to click the links since it will not work :))
Let's got to search ImageCarousel and we will find ImageCarousel.kt. Let's find CarouselListener in there
We can see that when CarouselListener is set, it will immediately assign to adapter?.listener (Just ignore the "?" sign if you are not familiar with Kotlin)
2nd question: What is adapter here?
Ans from the same file:
private var adapter: FiniteCarouselAdapter? = null
3rd question: What is FiniteCarouselAdapter?
Ans: Its a RecyclerView.Adapter
open class FiniteCarouselAdapter(
...
) : RecyclerView.Adapter<FiniteCarouselAdapter.MyViewHolder>() {
Last question: How is it related to FiniteCarouselAdapter#listener/CarouselListener/adapter?.listener?
When the RecyclerView#Adapter requires to call the ViewHolder method, it will call to CarouselListener methods instead.
In FiniteCarouselAdapter:
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
...
// Init listeners
listener?.onBindViewHolder(
holder.binding,
item,
realItemPosition
)
As CarouselListener is an interface, the method implementation will be defined in the KotlinActivity instead.
In KotlinActivity:
The above code in your question : )
I have a main activity with a heading and a search field (edit text), I want to be able to search and the results are immediately shown in the fragment, like an onChange instead of waiting for the user to click a button to filter results. (which is in the activity).
I can get it working if I include the Edit Text in my fragment too, but I don't want it that way for design purposes, I'd like to retrieve the user values as they are typed from the activity, and get them in my fragment to filter results
I've tried Bundles but could not get it working, and also not sure If i could use Bundles to get the results as they are being input.
Here's a screenshot to help understand better
You can make it happen using ViewModel + MVVM architecture.
MainActivity:
binding.editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
viewModel.updateSearchText(s)
}
})
ViewModel:
private val _searchText = MutableLiveData<Editable?>()
val searchText: LiveData<Editable?> get() = _searchText
fun updateSearchText(text: Editable?) {
_searchText.value = s
}
Fragment:
searchText.observe(viewLifecycleOwner) {
// TODO: handle the searched query using [it] keyword.
}
If you don't know what View Model is or how to implement it, use the official Google tutorial: https://developer.android.com/codelabs/basic-android-kotlin-training-viewmodel
Another way to achieve this (besides using an Android ViewModel) is use the Fragment Result API.
For instance, if you place the EditText into a fragment (let's call it QueryFragment), you can get the result of the QueryFragment in your SearchResults fragment like so:
// In QueryFragment
editText.addTextChangedListener(object: TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable?) {
setFragmentResult("searchQueryRequestKey", bundleOf("searchQuery" to s.toString()))
}
})
// In SearchResultsFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve the searched string from QueryFragment
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("searchQueryRequestKey") { requestKey, bundle ->
val searchQuery = bundle.getString("searchQuery")
// Perform the search using the searchQuery and display the search results
}
}
Edit
I've created a demo project on Github showing the exact problem. Git Project.
I've written an expandable recyclerView in Kotlin Every row has a play button which uses TextToSpeech. The text of the play button should change to stop whilst its playing, then change back to play when it finishes.
When I call notifyItemChanged within onStart and onDone of setOnUtteranceProgressListener, onBindViewHolder is not called and the rows in the recyclerView will no longer expand and collapse correctly.
t1 = TextToSpeech(context, TextToSpeech.OnInitListener { status ->
if (status != TextToSpeech.ERROR) {
t1?.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {
recyclerView.adapter.notifyItemChanged(position)
}
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
onDone(utteranceId)
}
override fun onDone(utteranceId: String?) {
val temp = position
position = -1
recyclerView.adapter.notifyItemChanged(temp)
}
override fun onError(utteranceId: String?) {}
// override fun onError(utteranceId: String?, errorCode: Int) {
// super.onError(utteranceId, errorCode)
// }
})
}
})
onBindViewHolder:
override fun onBindViewHolder(holder: RabbanaDuasViewHolder, position: Int){
if (Values.playingPos == position) {
holder.cmdPlay.text = context.getString(R.string.stop_icon)
}
else {
holder.cmdPlay.text = context.getString(R.string.play_icon)
}
}
How can I call notifyItemChanged(position) from within setOnUtteranceProgressListener or what callback can I use so that notifyItemChanged only executes when it's safe to execute?
I tried to replicate your issue and I came to know that it is not working because methods of UtteranceProgressListener is not called on main thread and that's why onBindViewHolder method of the adapter is not called.
This worked for me and should work for you too:
Use runOnUiThread{} method to perform actions on RecyclerView like this:
t1.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onError(utteranceId: String?) {
}
override fun onStart(utteranceId: String?) {
runOnUiThread {
recyclerView.adapter?.notifyItemChanged(position)
}
}
override fun onStop(utteranceId: String?, interrupted: Boolean) {
super.onStop(utteranceId, interrupted)
onDone(utteranceId)
}
override fun onDone(utteranceId: String?) {
val temp = position
position = -1
runOnUiThread {
recyclerView.adapter?.notifyItemChanged(temp)
}
}
}
I solved the problem using runOnUiThread thanks to Birju Vachhani.
For a full working demo of not just the problem I had, but how to correctly expand and collapse rows in a RecyclerView (no onClick events in onBindViewHolder) see my Gitlab Demo Project.
java.lang.VerifyError: .../utils/KotlinViewExtKt$animateFadeOut$1
Getting that error while running app on emulator PRE Lolipop (<21 api)
Function causing the trouble:
fun View.animateFadeOut(animDuration: Long = 250) {
this.animate()
.alpha(0F)
.setDuration(animDuration)
.setListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
super.onAnimationEnd(animation, isReverse)
show(false)
}
override fun onAnimationEnd(p0: Animator?) {
show(false)
}
override fun onAnimationCancel(p0: Animator?) {
}
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
}
override fun onAnimationStart(p0: Animator?) {
}
})
.start()
}
fun View.show(show: Boolean) {
val vis = if (show) View.VISIBLE else View.GONE
if (visibility != vis) {
visibility = vis
}
}
Pointing on .setListener line.
Works perfectly on 21+ api.
AS versio: 3.0.1. Kotlin version: 1.2.21 (tried 1.1.51).
What could be the reason? My bad or kotlin?
Multidex is enabled.
Solution
Based on this issue and this fix:
ViewExtension.kt
fun View.animateFadeOut(animDuration: Long = 250L) {
this.animate()
.alpha(0F)
.setDuration(animDuration)
.setListener(object : AbstractAnimatorListener() {
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
super.onAnimationEnd(animation, isReverse)
show(false)
}
override fun onAnimationEnd(p0: Animator?) {
show(false)
}
})
.start()
}
fun View.show(show: Boolean) {
val vis = if (show) View.VISIBLE else View.GONE
if (visibility != vis) {
visibility = vis
}
}
AbstractAnimatorListener.kt
abstract class AbstractAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(p0: Animator?) {}
override fun onAnimationCancel(p0: Animator?) {}
override fun onAnimationStart(p0: Animator?) {}
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
onAnimationEnd(animation)
}
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
onAnimationStart(animation)
}
}
Explanation
Try to remove these methods added in API 26 using Java 8 Defaults and use alternative animation:
Animator.AnimatorListener:
onAnimationEnd added in API level 26
void onAnimationEnd (Animator animation, boolean isReverse)
onAnimationStart added in API level 26
void onAnimationStart (Animator animation, boolean isReverse)
These methods can be overridden, though not required, to get the additional play direction info when an animation starts.
AnimatorSet:
Starting in Android 8.0 (API 26) the AnimatorSet API supports
seeking and playing in reverse.
Note:
/**
* Skipping calling super when overriding this method results in
* {#link #onAnimationStart(Animator)} not getting called.
*/
default void onAnimationStart(Animator animation, boolean isReverse) {
onAnimationStart(animation);
}
I need to add this to my build.gradle file to test it
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}
or replace
super.onAnimationEnd(animation, isReverse)
by
onAnimationEnd(animation)
to avoid the error:
Super calls to Java default methods are prohibited in JVM target 1.6.
Recompile with '-jvm-target 1.8'
And add the next line to my gradle.properties file thanks to this answer
android.enableD8=true
to avoid the exception:
com.android.dx.cf.code.SimException: default or static interface
method used without --min-sdk-version >= 24
It compiles now, launching a kitKat emulator just now...
It works, and MultiDex is also enabled in my project.
Sorry, I cannot reproduce it.
Alternatively, remove the two methods using Java 8 defaults, it also works.
Note2:
Arpan suggestion about AnimatorListenerAdapter would work but it's not necessary.
You can remove those methods, change the animation and create:
object EmptyAnimatorListener : Animator.AnimatorListener {
override fun onAnimationRepeat(p0: Animator?) {}
override fun onAnimationEnd(p0: Animator?) {}
override fun onAnimationCancel(p0: Animator?) {}
override fun onAnimationStart(p0: Animator?) {}
}
And use delegation like this:
.setListener(object : Animator.AnimatorListener by EmptyAnimatorListener {
override fun onAnimationStart(animation: Animator) {
// Do something
}
override fun onAnimationEnd(animation: Animator) {
// Do something
}
})
Note3:
Reproduced adding it to an extension function:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: ...android, PID: 3409
java.lang.VerifyError: com/.../ui/common/extension/android/view/ViewExtensionKt$animateFadeOut$1
Removing the Api 26 methods using Java Defaults solves the issue.
Replacing the super call calling your onAnimationEnd also solves it:
override fun onAnimationEnd(animation: Animator?, isReverse: Boolean) {
onAnimationEnd(animation)
}
Calling super.onAnimationStart(animation: Animator?, isReverse: Boolean) requires API 26:
Decompiling Kotlin Bytecode shows these methods cannot be resolved:
I am trying to intercept every view widget that is being inflated in my activity and override the setText functionality of that view if it exists.
So if a TextView has a android:text="bla" in the XML layout i want to be able to intercept that and maybe add a ** at the end of all texts being set from the xml.
One way that seems to be close to what i need is to use a Custom Layout inflator.
LayoutInflaterCompat.setFactory(layoutInflator, InflatorOnSteriods(this))
and the in my InflatorOnSteriods to override onCreateView and then intercept all views there.. this approach doesn't seem to work at all. onCreateView is never called.
I tried also to use cloneInContext
LayoutInflaterCompat.setFactory(layoutInflater.cloneInContext(this), InflatorOnSteriods(this))
But no luck as well, maybe my approach is totally wrong i am also open to a different way where i can intercept all views being presented and to be specific set a certain attribute on that view. It is really important to make sure that i will be the last one changing that view and make sure the system respects my changes and wont override them later.
Update:
Although i don't think its relevant; Code of InflatorOnSteroids.kt
class InflatorOnSteriods(val appCompatActivity: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View, name: String, context: Context, attrs: AttributeSet): View {
var result: View
if (TextUtils.equals(name, "DebugDrawerLayout")) {
result = ImageView(context, attrs)
}
result = appCompatActivity.onCreateView(name, context, attrs)
if (result == null) {
// Get themed views from app compat
result = appCompatActivity.delegate.createView(parent, name, context, attrs)
}
return result
}
}
After some time troubleshooting my solution i finally managed to achieve what i wanted with the inflator factory solution.
First i create an abstract activity that has a custom inflator set to it.
abstract class SteroidsActivity : AppCompatActivity() {
var mInflater: LayoutInflater? = null
abstract fun getActivityLayout(): Int
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mInflater = layoutInflater?.cloneInContext(this)
LayoutInflaterCompat.setFactory(mInflater, InflaterOnSteroids(this))
setContentView(getActivityLayout())
}
override fun getLayoutInflater(): LayoutInflater? {
return mInflater
}
override fun getSystemService(name: String): Any? {
if (name == LAYOUT_INFLATER_SERVICE) {
if (mInflater == null) {
mInflater = super.getSystemService(name) as LayoutInflater
}
return mInflater
}
return super.getSystemService(name)
}
}
Second thing that you need to do is create your custom inflator factory
class InflaterOnSteroids(appCompatActivity1: AppCompatActivity) : LayoutInflaterFactory {
override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {
//Do stuff here and return a view
return null
}
}
The problem with my code was that it was always crashing with a weird error that i couldn't troubleshoot until i realised that i need to add a ? after View since i'm using kotlin and parent view can be null :)
Happy programming
Good reference can be found here