common android utilities
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.hereisderek:android-util:-SNAPSHOT'
}
import com.github.hereisderek.androidutil.obj.lazyInit
class ImageEditorPreviewViewHolder(context: Context) : RecyclerView.ViewHolder(AppCompatImageView(context).apply {
layoutParams = LAYOUT_PARAMS.get(context)
}) {
companion object {
private val LAYOUT_PARAMS = lazyInit<FrameLayout.LayoutParams, Context>{ context ->
context.resources.let {
FrameLayout.LayoutParams(
it.getDimension(R.dimen.preview_slider_item_width).toInt(),
it.getDimension(R.dimen.preview_slider_item_height).toInt()
)
}
}
}
}
private val mSolidColorRoundBitmapGenerator by lazy {
SolidColorRoundBitmapGenerator(Injection.provideBitmapPoolUtil(this))
}
private suspend fun debugSolidColorCircularBitmapWithBorder(radius: Int, borderWidth: Int, shadowWidth: Int) : Bitmap {
val (time, image) = withContext(ioContext) {
measureNanoTime {
mSolidColorRoundBitmapGenerator.solidColorCircularBitmapWithBorder(
ColorUtil.getNextRainBowColor(this@MainActivity),
radius,
Color.WHITE,
borderWidth.toFloat(),
shadowWidth = shadowWidth.toFloat()
)
}
}
val message = "Bitmap with border generated, width:${image.width}, height:${image.height}, took: $time nanoseconds, ${time / 1000_000L} milliseconds"
Timber.d(message)
Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
return image
}
private suspend fun debugSolidColorCircularBitmapWithBorderParallel(radius: Int, borderWidth: Int, shadowWidth: Int, parallel: Int = 5) : List<Bitmap> {
return (0 until parallel).map {
async {
measureNanoTime {
debugSolidColorCircularBitmapWithBorder(radius, borderWidth, shadowWidth)
}
}
}.map { deferred ->
deferred.await()
}.mapIndexed { index, result ->
val (time, image) = result
val message = "$index - with border result returned, width:${image.width}, height:${image.height}, took: $time nanoseconds, ${time / 1000_000L} milliseconds"
Timber.d(message)
Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
image
}
}
private val mPaintPool = ObjectPool(Paint::class.java, MAX_POOL_SIZE){
isAntiAlias = true
style = Paint.Style.FILL
}
private val mCanvasPool = ObjectPool(MAX_POOL_SIZE){
Canvas()
}
fun solidColorCircularBitmap(
@ColorInt color: Int,
radius: Int,
config: Bitmap.Config = mConfig
) : Bitmap {
val bitmap = createEmptyBitmap(radius * 2, config = config)
val radiusF = radius.toFloat()
mCanvasPool.acquire { canvas ->
mPaintPool.acquire { paint ->
canvas.setBitmap(bitmap)
paint.style = Paint.Style.FILL
paint.color = color
canvas.drawCircle(radiusF, radiusF, radiusF, paint)
}
}
return bitmap
}
A CoroutineScope implementation that provides mainContext, ioContext, exposes val job for children task creation, handles task
cancellation (by registering lifecycle fun registerLifeCycle(owner: LifecycleOwner)
) or simply calling fun cancelChildrenJobs()
abstract class BaseFragment : Fragment(), ICoroutineScope by CoroutineScopeImpl() {
val requireActivity get() = requireActivity()
val requireContext get() = requireContext()
val appCompatActivity get() = activity as? AppCompatActivity
override fun onAttach(context: Context) {
super.onAttach(context)
registerLifeCycle(this)
}
fun coroutineCall() = launch{
async{
// ... some async tasks
}
}
}
well, it's just like any other pool project, but it takes a generator object so it's guaranteed that fun acquire(): T
won't return
null
value (unless T is a nullable)
I'll probably add use cases later but I'm too lazy so I'll just paste a test case here instead (SynchronizedExpandablePool is the thread safe version of the same thing)
@Test
fun testTrim() {
var index = 0
val pool = SimpleExpandablePool{
(index++).toString()
}
(0..10).forEach {
assertEquals(it, pool.size)
pool.release(it.toString())
assertEquals(it + 1, pool.size)
}
pool.trimToSize(4)
assertEquals(4, pool.size)
}
you can also call fun <R> use(block: (T) -> R): R
for temporary use (automatically recycled right after)
also an extension method for creating a pool object
@Test
fun testCreation() {
class DummyClass(val index : Int)
var index = 0
val pool = pool(LazyThreadSafetyMode.SYNCHRONIZED) {
DummyClass(index++)
}
pool.use {
assertEquals(1, index)
assertEquals(it.index, 0)
}
assertEquals(1, pool.size)
assertEquals(0, pool.acquire().index)
}
a self-contained, reusable computed obj that only update when requested but marked as dirty (lazy calculate)
import com.github.hereisderek.androidutil.obj.MutableObj
// probably not the best use case but this is what I have right now
private val mViewCenterVO = MutableObj<PointF?>{
val laidOut = ViewCompat.isLaidOut(this)
Timber.d("laidOut:$laidOut, width:${this.width}, height:${this.height}, isReady:$isReady")
if (laidOut && isReady) {
(it ?: PointF()).apply {
x = width / 2f
y = height / 2f
}
} else null
}
* fun <T> Cursor.toArrayList(block: (Cursor) -> T): ArrayList<T>
* fun Cursor.toArrayList(close: Boolean = false, block: (Cursor) -> T): ArrayList<T>
* fun <T> Cursor.toArrayListIndexed(close: Boolean = false, block: (Cursor, index: Int) -> T): ArrayList<T>
* fun <T> Cursor.toSelfArrayList(close: Boolean = false, block: Cursor.() -> T): ArrayList<T>
* Cursor.toSelfArrayListIndexed(close: Boolean = false, block: Cursor.(index: Int) -> T): ArrayList<T>
* fun <K, V> Cursor.toArrayMapIndexed(close: Boolean = false, block: (Cursor, index: Int) -> Pair<K, V>) : Map<K, V>
* fun <K, V> Cursor.toSelfArrayMapIndexed(close: Boolean = false, block: Cursor.(index: Int) -> Pair<K, V>) : Map<K, V>
example:
import com.github.hereisderek.androidutil.misc.toArrayList
/// read
fun getStatus(
table: TableClass,
msgId: String
) : List<MsgStatus> = readableDatabase.use { db ->
db.query(true, table.tableName, null, "$KEY_MSG_ID=?", arrayOf(msgId), null, null, null, null)
.toArrayList(true) {
MsgStatus(
table,
getInt(getColumnIndex(KEY_ID)),
getString(getColumnIndex(KEY_MSG_ID)),
getString(getColumnIndex(KEY_BY)),
getLong(getColumnIndex(VALUE_EVENT_SENT)),
getLong(getColumnIndex(VALUE_EVENT_DELIVERED)),
getLong(getColumnIndex(VALUE_EVENT_READ))
)
}
}
8. SingleFragmentActivity (you can override theme/style by adding the activity in your manifest file)
To view other start options, see here
// called from within an activity or a fragment for example
// you can use an auto generated requestCode, or specify yours
val requestCode = SingleFragmentActivity.startForResult(this, MyFragment::class.java){
putString("arg1", "arg1_value")
}
fun <T> useOrCreateAndClose(
generator: ()->Closeable,
_closeable: Closeable? = null,
action: Closeable.(created: Boolean)-> T
) : T
To use an existing Closeable
Object (such as SQLiteDatabase
) or create a new one by invoking the generator, if it was from the generator, the newly created Closeable
will be automatically closed after used,
otherwise, you will also be given a chance to manually close it in the action{}
(if you want to that is):
this can be used for sharing the same database object on a chain of methods so we won't need to keep requesting new database object
Just to close a Closeable?
object quietly, pass along the exception to the handler or suppress it if none given
10. CollectionExt.flatMap() with CollectionExtKtTest.flatMapTo(ArrayList(), byKey, transform) and others
val test = mapOf(
1 to intArrayOf(1, 2, 3, 4),
2 to intArrayOf(11, 12, 13, 14),
// note: map doesn't allow duplicated keys
// 2 to intArrayOf(18, 19, 17, 16),
4 to intArrayOf(1111, 2222, 3333, 4444),
5 to intArrayOf(5555)
)
val result = test.flatMap(
byKey = intArrayOf(2, 3).toTypedArray()
){
ArrayList<Int>().apply {
addAll(it.component2().toTypedArray())
}
}
// result: 11, 12, 13, 14, 1, 2, 3, 4, 1111, 2222, 3333, 4444, 5555
DelegateRW<T>
is an interface that can be used as a delegate(var variable by DelegateRW<T>
) provides four methods to enable read/write access and notify listeners when changes are made
interface DelegateRW<T> {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
operator fun plusAssign(onChangeListener: OnChangedListener<T>)
fun subscribe(onChangeListener: OnChangedListener<T>) = this.also { this += onChangeListener }
}
concrate implementations are as follows
- DelegateRWUnSafe (and it's threadsafe version: DelegateRWSafe)
- DelegateLazyRW (and it's threadsafe version: DelegateLazyRWSafe)
also with a helper method
fun <T> delegateWR(
lazyValue: () -> T,
mode : LazyThreadSafetyMode = LazyThreadSafetyMode.NONE,
vararg onChangeListener: OnChangedListener<T>
) : DelegateRW<T>
fun <T> delegateWR(
init: T,
mode : LazyThreadSafetyMode = LazyThreadSafetyMode.NONE,
onChangeListener: OnChangedListener<T>? = null
) = when(mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> DelegateRWSafe(init, onChangeListener)
else -> DelegateRWUnSafe(init, onChangeListener)
}
fun <V : View, T> V.onChangeViewInvalidate(
init: T,
mode : LazyThreadSafetyMode = LazyThreadSafetyMode.NONE,
onChangeListener: OnChangedListener<T>? = null
) : DelegateRW<T> = delegateWR(init, mode, onChangeListener).apply {
subscribe { _, _ -> invalidate() }
}
class CustomView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {
// whenever changed, it will also trigger view invalidation
var size: Int by this.onChangeViewInvalidate(10).also {
it.subscribe { old, new ->
// you can also add extra layer of listener for value changes
println("size value changed from:$old to:$new")
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// use the value of size for drawing...
}
}