Skip to content

Commit

Permalink
MBL-1318: Spiking crash PicassoDrawable.draw trying to draw too large…
Browse files Browse the repository at this point in the history
… bitmap mitigation (#2015)
  • Loading branch information
Arkariang authored Apr 15, 2024
1 parent 1230aee commit c39100b
Show file tree
Hide file tree
Showing 19 changed files with 155 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import androidx.activity.ComponentActivity
import androidx.activity.viewModels
import androidx.core.view.isGone
import com.kickstarter.databinding.ActivityEditProfileBinding
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.SwitchCompatUtils
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.getEnvironment
import com.kickstarter.libs.utils.extensions.isFalse
import com.kickstarter.models.User
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
import com.kickstarter.ui.extensions.showSnackbar
import com.kickstarter.viewmodels.EditProfileViewModel
import com.squareup.picasso.Picasso
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable

Expand All @@ -40,7 +39,7 @@ class EditProfileActivity : ComponentActivity() {
this.viewModel.outputs.userAvatarUrl()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { url ->
Picasso.get().load(url).transform(CircleTransformation()).into(binding.avatarImageView)
binding.avatarImageView.loadCircleImage(url)
}.addToDisposable(disposables)

this.viewModel.outputs.user()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import com.kickstarter.libs.BaseActivity
import com.kickstarter.libs.RecyclerViewPaginator
import com.kickstarter.libs.qualifiers.RequiresActivityViewModel
import com.kickstarter.libs.rx.transformers.Transformers.observeForUI
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.ApplicationUtils
import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.libs.utils.extensions.getProjectIntent
import com.kickstarter.models.Project
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.adapters.ProfileAdapter
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.viewmodels.ProfileViewModel
import com.squareup.picasso.Picasso

@RequiresActivityViewModel(ProfileViewModel.ViewModel::class)
class ProfileActivity : BaseActivity<ProfileViewModel.ViewModel>() {
Expand All @@ -46,7 +45,7 @@ class ProfileActivity : BaseActivity<ProfileViewModel.ViewModel>() {
this.viewModel.outputs.avatarImageViewUrl()
.compose(bindToLifecycle())
.compose(observeForUI())
.subscribe { url -> Picasso.get().load(url).transform(CircleTransformation()).into(binding.avatarImageView) }
.subscribe { url -> binding.avatarImageView.loadCircleImage(url) }

this.viewModel.outputs.backedCountTextViewHidden()
.compose(bindToLifecycle())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,14 @@ import com.kickstarter.libs.Build
import com.kickstarter.libs.KSString
import com.kickstarter.libs.Logout
import com.kickstarter.libs.featureflag.FlagKey
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.ApplicationUtils
import com.kickstarter.libs.utils.ViewUtils
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.getEnvironment
import com.kickstarter.ui.SharedPreferenceKey
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
import com.kickstarter.viewmodels.SettingsViewModel
import com.squareup.picasso.Picasso
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable

Expand Down Expand Up @@ -86,8 +85,7 @@ class SettingsActivity : AppCompatActivity() {
this.viewModel.outputs.avatarImageViewUrl()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { url ->
Picasso.get().load(url).transform(CircleTransformation())
.into(binding.profilePictureImageView)
binding.profilePictureImageView.loadCircleImage(url)
}
.addToDisposable(disposables)

Expand Down
162 changes: 116 additions & 46 deletions app/src/main/java/com/kickstarter/ui/extensions/ImageViewExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,75 +3,145 @@ package com.kickstarter.ui.extensions
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.kickstarter.R
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.extensions.isKSApplication
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import javax.sql.DataSource

fun ImageView.loadCircleImage(url: String?) {
url?.let {
if (it.isBlank()) { // - load with drawable
Picasso.get()
.load(R.drawable.circle_grey_500)
.transform(CircleTransformation())
.into(this)
} else { // - load with url string
Picasso.get()
.load(it)
.placeholder(R.drawable.circle_grey_500)
.transform(CircleTransformation())
.into(this)
try {
if (it.isBlank()) { // - load with drawable
Glide.with(context)
.load(ColorDrawable(Color.TRANSPARENT))
.circleCrop()
.into(this)
} else { // - load with url string
Glide.with(context)
.load(it)
.placeholder(ColorDrawable(Color.TRANSPARENT))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.circleCrop()
.into(this)
}
} catch (e: Exception) {
// - Empty by default in case or error
this.setImageResource(R.drawable.image_placeholder)
FirebaseCrashlytics.getInstance().setCustomKey("ImageView.loadCircleImage", " with url: $it ${e.message ?: ""}")
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}

fun ImageView.loadImage(url: String?) {
url?.let {
Picasso
.get()
.load(it)
.into(this)
try {
Glide.with(context)
.load(url)
.placeholder(ColorDrawable(Color.TRANSPARENT))
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.into(this)
} catch (e: Exception) {
this.setImageResource(R.drawable.image_placeholder)
FirebaseCrashlytics.getInstance().setCustomKey("ImageView.loadImage", " with url: $it ${e.message ?: ""}")
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}

fun ImageView.loadImageWithResize(
url: String?,
targetImageWidth: Int,
targetImageHeight: Int,
placeholder: Drawable
) {
url?.let {
try {
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.apply(RequestOptions().override(targetImageWidth, targetImageHeight))
.centerCrop()
.placeholder(placeholder)
.into(this)
} catch (e: Exception) {
this.setImageResource(R.drawable.image_placeholder)
FirebaseCrashlytics.getInstance().setCustomKey("ImageView.loadImageWithResize", " with url: $it ${e.message ?: ""}")
FirebaseCrashlytics.getInstance().recordException(e)
}
}
}
fun ImageView.loadImage(url: String?, context: Context, imageViewPlaceholder: AppCompatImageView? = null) {
val target = this
if (context.applicationContext.isKSApplication()) {
Picasso
.get()
.load(url)
.into(
this,
object : Callback {
override fun onSuccess() {
imageViewPlaceholder?.setImageDrawable(target.drawable)
}
url?.let {
val targetView = this
if (context.applicationContext.isKSApplication()) {
try {
Glide.with(context)
.load(url)
.listener(object : RequestListener<Drawable> {
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: com.bumptech.glide.load.DataSource?,
isFirstResource: Boolean
): Boolean {
imageViewPlaceholder?.setImageDrawable(resource)
return isFirstResource
}

override fun onError(e: Exception?) {
target.setImageDrawable(null)
imageViewPlaceholder?.setImageDrawable(null)
}
}
)
} else {
this.setImageResource(R.drawable.image_placeholder)
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
targetView.setImageDrawable(null)
imageViewPlaceholder?.setImageDrawable(null)
return isFirstResource
}
})
.placeholder(ColorDrawable(Color.TRANSPARENT))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(url)
.into(this)
} catch (e: Exception) {
this.setImageResource(R.drawable.image_placeholder)
FirebaseCrashlytics.getInstance().setCustomKey("ImageView.loadImageWithResize", " with url: $it ${e.message ?: ""}")
FirebaseCrashlytics.getInstance().recordException(e)
}
} else {
this.setImageResource(R.drawable.image_placeholder)
}
}
}

fun ImageView.loadGifImage(url: String?, context: Context) {
if (context.applicationContext.isKSApplication()) {
Glide.with(context)
.asGif()
.placeholder(ColorDrawable(Color.TRANSPARENT))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(url)
.into(this)
} else {
this.setImageResource(R.drawable.image_placeholder)
url?.let {
if (context.applicationContext.isKSApplication()) {
try {
Glide.with(context)
.asGif()
.placeholder(ColorDrawable(Color.TRANSPARENT))
.diskCacheStrategy(DiskCacheStrategy.ALL)
.load(url)
.into(this)
} catch (e: Exception) {
this.setImageResource(R.drawable.image_placeholder)
FirebaseCrashlytics.getInstance().setCustomKey("ImageView.loadImageWithResize", " with url: $url ${e.message ?: ""}")
FirebaseCrashlytics.getInstance().recordException(e)
}
} else {
this.setImageResource(R.drawable.image_placeholder)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.kickstarter.R
import com.kickstarter.databinding.FragmentProjectOverviewBinding
import com.kickstarter.libs.Configure
import com.kickstarter.libs.KSString
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.ApplicationUtils
import com.kickstarter.libs.utils.DateTimeUtils
import com.kickstarter.libs.utils.SocialUtils
Expand All @@ -36,14 +35,14 @@ import com.kickstarter.ui.ArgumentsKey
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.activities.ProjectSocialActivity
import com.kickstarter.ui.data.ProjectData
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.extensions.setClickableHtml
import com.kickstarter.ui.extensions.startCreatorBioWebViewActivity
import com.kickstarter.ui.extensions.startLoginActivity
import com.kickstarter.ui.extensions.startProjectUpdatesActivity
import com.kickstarter.ui.extensions.startReportProjectActivity
import com.kickstarter.ui.extensions.startRootCommentsActivity
import com.kickstarter.viewmodels.projectpage.ProjectOverviewViewModel.ProjectOverviewViewModel
import com.squareup.picasso.Picasso
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import org.joda.time.DateTime
Expand Down Expand Up @@ -224,10 +223,7 @@ class ProjectOverviewFragment : Fragment(), Configure {
.observeOn(AndroidSchedulers.mainThread())
.subscribe { url: String? ->
url?.let {
Picasso.get()
.load(it)
.transform(CircleTransformation())
.into(binding.projectSocialImage)
binding.projectSocialImage.loadCircleImage(it)
}
}
.addToDisposable(disposables)
Expand Down Expand Up @@ -393,10 +389,7 @@ class ProjectOverviewFragment : Fragment(), Configure {
}

private fun setAvatar(url: String) {
Picasso.get()
.load(url)
.transform(CircleTransformation())
.into(binding.avatar)
binding.avatar.loadCircleImage(url)
}

private fun setConvertedCurrencyView(pledgedAndGoal: Pair<String, String>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import android.view.View
import com.kickstarter.R
import com.kickstarter.databinding.ActivitySampleFriendFollowViewBinding
import com.kickstarter.libs.rx.transformers.Transformers
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.models.Activity
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.viewmodels.ActivitySampleFriendFollowViewHolderViewModel
import com.squareup.picasso.Picasso
import io.reactivex.disposables.CompositeDisposable

class ActivitySampleFriendFollowViewHolder(
Expand All @@ -32,9 +31,7 @@ class ActivitySampleFriendFollowViewHolder(
.subscribe {
it.user()?.let { user ->
user.avatar().small().let { url ->
Picasso.get().load(url)
.transform(CircleTransformation())
.into(binding.activityImage)
binding.activityImage.loadCircleImage(url)
}

binding.activityTitle.text = ksString.format(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.kickstarter.databinding.ActivitySampleProjectViewBinding
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.models.Activity
import com.kickstarter.models.Project
import com.kickstarter.ui.extensions.loadImage
import com.kickstarter.viewmodels.ActivitySampleProjectViewHolderViewModel
import com.squareup.picasso.Picasso
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable

Expand Down Expand Up @@ -40,9 +40,7 @@ class ActivitySampleProjectViewHolder(
activity.project()?.let { project ->
val photo = project.photo()
photo?.let {
Picasso.get()
.load(photo.little())
.into(binding.activityImage)
binding.activityImage.loadImage(it.little())
}
binding.activityTitle.text = project.name()
val activitySubtitleText = when (activity.category()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package com.kickstarter.ui.viewholders

import com.kickstarter.R
import com.kickstarter.databinding.ActivityFriendBackingViewBinding
import com.kickstarter.libs.transformations.CircleTransformation
import com.kickstarter.libs.utils.SocialUtils
import com.kickstarter.models.Activity
import com.squareup.picasso.Picasso
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.extensions.loadImage

class FriendBackingViewHolder(
private val binding: ActivityFriendBackingViewBinding,
Expand All @@ -25,17 +25,12 @@ class FriendBackingViewHolder(
val projectCategory = activityProject.category() ?: return
val projectPhoto = activityProject.photo() ?: return
activityUser.avatar().small()?.let {
Picasso.get()
.load(it)
.transform(CircleTransformation())
.into(binding.avatar)
binding.avatar.loadCircleImage(it)
}

binding.creatorName.text = ksString.format(context.getString(R.string.project_creator_by_creator), "creator_name", projectCreator.name())
binding.projectName.text = activityProject.name()
Picasso.get()
.load(projectPhoto.little())
.into(binding.projectPhoto)
binding.projectPhoto.loadImage(projectPhoto.little())
binding.title.text = SocialUtils.friendBackingActivityTitle(
context,
activityUser.name(),
Expand Down
Loading

0 comments on commit c39100b

Please sign in to comment.