bEarlier than persevering with, this subject assumes that the reader already is aware of the idea of Kotlin Circulation, the Android Media Retailer/Content material Resolver.
Drawback?
- MediaStore API on android is from historic instances, when cell phones did not have sturdy and every little thing is simply communication.
- It is a trouble to jot down the identical code again and again.
- Dealing with error susceptible SQL queries.
- MediaStore API not concurrently.
Sequential asynchronous knowledge stream emits values and completes usually or with exception.
2. Content material Resolver
In line with doc, ContentResolver
Is one “class that provides functions entry to the content material mannequin”. ContentResolvers expose strategies to work together with, fetch, or modify content material served from the next:
- Put in Purposes (
content material://
URI scheme) - File system (
file://
URI scheme) - Helps Android-provided APIs (
android.useful resource://
URI scheme)
learn than…
3. Media retailer
Contract between the media supplier and the applying. Incorporates definitions for supported URIs and columns.
Media suppliers present an listed assortment of fashionable media sorts, akin to
Audio
,Video
AndPictures
, from any connected storage gadget. Every assortment is organized based mostly on the underlying asset’s main MIME sort; For instance,picture/*
Content material is listed underneathPictures
. TheRecordsdata
collections offers a sweeping view of all collections and doesn’t filter by MIME sort. learn extra
4. Callback movement
It’s a particular sort of Koltin Circulation. With callbackFlow
we will convert any callback based mostly API into streams.
With the assistance of reactive programming like Kotlin Streams & the magic of prolonged features we will simply remedy the above issues in a single shot.
Very first thing, write two extensions perform on Android Content material Resolver name query2 And the Observer as proven under.
tthe perform under is a straightforward perform suspended perform run on Dispatcher.Default Background. This perform returns pointer Factor.
- Uri — Content material Uri in ContentResolver akin to MediaStore.Audio.EXTERNAL_CONTENT_URI.
- mat: Array of columns to return.
- choose: Choice circumstances and correspondence debate.
- order: Order of Content material.
- ascending: Enhance/lower situation.
Every thing else is self-explanatory.
/**
* A complicated of [ContentResolver.query]
* @see ContentResolver.question
* @param order legitimate column to make use of for orderBy.
*/
droop enjoyable ContentResolver.query2(
uri: Uri,
projection: Array? = null,
choice: String = DUMMY_SELECTION,
args: Array? = null,
order: String = MediaStore.MediaColumns._ID,
ascending: Boolean = true,
offset: Int = 0,
restrict: Int = Int.MAX_VALUE
): Cursor? {
return utilizing(Dispatchers.Default) {
// use solely above android 10
if (Construct.VERSION.SDK_INT >= Construct.VERSION_CODES.O) {
// compose the args
val args2 = Bundle().apply {
// Restrict & Offset
putInt(ContentResolver.QUERY_ARG_LIMIT, restrict)
putInt(ContentResolver.QUERY_ARG_OFFSET, offset)
// order
putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, arrayOf(order))
putInt(
ContentResolver.QUERY_ARG_SORT_DIRECTION,
if (ascending) ContentResolver.QUERY_SORT_DIRECTION_ASCENDING else ContentResolver.QUERY_SORT_DIRECTION_DESCENDING
)
// Choice and groupBy
if (args != null) putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, args)
// add choice.
// TODO: Think about including group by.
// at present I skilled errors in android 10 for groupBy and arg groupBy is supported
// above android 10.
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, choice)
}
question(uri, projection, args2, null)
}
// under android 0
else {
//language=SQL
val order2 =
order + (if (ascending) " ASC" else " DESC") + " LIMIT $restrict OFFSET $offset"
// compose the choice.
question(uri, projection, choice, args, order2)
}
}
}
Since Content material Resolver Discover of modifications via ContentObserver. We are able to use callbackFlow API to transform it to KoltinFlow. Right here is the code.
/**
* Register an observer class that will get callbacks when knowledge recognized by a given content material URI
* modifications.
*/
enjoyable ContentResolver.observe(uri: Uri) = callbackFlow {
val observer = object : ContentObserver(null) {
override enjoyable onChange(selfChange: Boolean) {
trySend(selfChange)
}
}
registerContentObserver(uri, true, observer)
// set off first.
trySend(false)
awaitClose {
unregisterContentObserver(observer)
}
}
That is it. Utilizing these two easy features we will create any helpful methodology out of it.
For instance
Easy creation extension property on pointer and ensure mat. The sound Is one entity class.
/**
* Maps [Cursor] at present place to Audio.
*/
personal inline val Cursor.toAudio
get() = Audio(
id = getLong(0),
title = getString(1) ?: MediaStore.UNKNOWN_STRING,
albumId = getLong(4),
knowledge = getString(8),
album = getString(3) ?: MediaStore.UNKNOWN_STRING,
artist = getString(2) ?: MediaStore.UNKNOWN_STRING,
composer = getString(6) ?: MediaStore.UNKNOWN_STRING,
mimeType = getString(10),
monitor = getInt(11),
dateAdded = getLong(5) * 1000,
dateModified = getLong(13) * 1000,
period = getInt(9),
measurement = getLong(12),
12 months = getInt(7)
)
personal val AUDIO_PROJECTION
get() = arrayOf(
MediaStore.Audio.Media._ID, //0
MediaStore.Audio.Media.TITLE, // 1
MediaStore.Audio.Media.ARTIST, // 2
MediaStore.Audio.Media.ALBUM, // 3
MediaStore.Audio.Media.ALBUM_ID, // 4
MediaStore.Audio.Media.DATE_ADDED, //5
MediaStore.Audio.Media.COMPOSER, // , // 6
MediaStore.Audio.Media.YEAR, // 7
MediaStore.Audio.Media.DATA, // 8
MediaStore.Audio.Media.DURATION, // 9
MediaStore.Audio.Media.MIME_TYPE, // 10
MediaStore.Audio.Media.TRACK, // 11
MediaStore.Audio.Media.SIZE, //12
MediaStore.Audio.Media.DATE_MODIFIED, // 14
)
Now create under extension ABOVE Content material Resolver. Return methodology Sound Record from the Media Retailer. This methodology takes up some debate element:
filter: non-obligatory filter string or null
order: Column to order in opposition to
and others are self-explanatory.
//language=SQL
personal const val DEFAULT_AUDIO_SELECTION = "${MediaStore.Audio.Media.IS_MUSIC} != 0"
personal const val DEFAULT_AUDIO_ORDER = MediaStore.Audio.Media.TITLE
/**
* @return [Audio]s from the [MediaStore].
*/
//@RequiresPermission(anyOf = [READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE])
droop enjoyable ContentResolver.getAudios(
filter: String? = null,
order: String = DEFAULT_AUDIO_ORDER,
ascending: Boolean = true,
offset: Int = 0,
restrict: Int = Int.MAX_VALUE
): Record
Now use the above talked about ContentResolver.obsever methodology to create stream from this code.
enjoyable ContentResolver.audios(
filter: String? = null,
order: String = DEFAULT_AUDIO_ORDER,
ascending: Boolean = true,
) = observe(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI).map {
getAudios(filter, order, ascending)
}
and Voila!! you might be completed and that is it.
Now use the strategies talked about above to unlock the Potential of comparable APIs