How to get a List<Bitmap> from asset folder in Kotlin - arraylist

How to init a list of Bitmaps from asset folder. I have a folder in assets called "images" with multiple PNG images:
E:\Computer Projects\Android\TaskProgress\app\src\main\assets\images
My code:
class Icons(val context: Context)
{
companion object
{
lateinit var iconList : MutableList<Bitmap>
}
init
{
iconList = ???
}

I implemented this by the following:
class Icons(var context: Context)
{
init
{
val assetManager = context.assets
try
{
var `is`: InputStream
var files = assetManager.list("boys")
for(i in files!!.indices)
{
`is` = assetManager.open("boys/" + files[i])
val bitmap = BitmapFactory.decodeStream(`is`)
iconListBoys.add(i, bitmap)
}
files = assetManager.list("girls")
for(i in files!!.indices)
{
`is` = assetManager.open("girls/" + files[i])
val bitmap = BitmapFactory.decodeStream(`is`)
iconListGirls.add(i, bitmap)
}
}
catch (e: IOException)
{
e.printStackTrace()
}
}
companion object
{
var iconListBoys: ArrayList<Bitmap> = ArrayList()
var iconListGirls: ArrayList<Bitmap> = ArrayList()
}
}

I am not in Android Environment right now, so below code might not work.
Please leave a comment if so.
You need to have Context to work with assets.
So rather than initializing iconList with declaration,
I'll add load() method to load the list of assets.
Icons class
Icons class will look like below if you add load() method.
class Icons {
companion object {
// initialize with empty list
var iconList = listOf<Bitmap>()
fun load(context: Context) {
// This is where we will load assets
}
}
}
load() method
First in load() method, we will get list of file names in assets/list.
context.getAssets() will return AssetManager object.
AssetManager.list(subPath) will return all files in subPath.
use map() to convert list of file name String to list of Bitmap.
AssetManager.open(filePath) to open InputStream from file.
BitmapFactory.decodeStream(stream) to create Bitmap from InputStream
With those, it will be
val am = context.getAssets()
iconList = am.list("images").map {
val inputStream = am.open("images/$it")
return BitmapFactory.decodeStream(inputStream)
}

Related

Kotlin: Edit icon dashboard of icons between fragments

I'm trying to figure out the most efficient way to structure this problem..
I'd like to click on the 'EDIT' icon in the dashboard of the MainFragment, display a DialogFragment, allow user to select/deselect up to 5 icons, save the selection, close the DialogFragment, and update the MainFragment.
Should I use MutableLiveData/Observer from a ViewModel? Or is there a better approach? I currently cannot figure out how to use the ViewModel approach correctly...
So far, this is the code I have:
MainFragment: https://i.stack.imgur.com/5fRt2.png
DialogFragment: https://i.stack.imgur.com/ZvW3d.png
ViewModel Class:
class IconDashboardViewModel() : ViewModel(){
var liveDataDashIcons: MutableLiveData<MutableList<String>> = MutableLiveData()
var liveItemData: MutableLiveData<String> = MutableLiveData()
// Observer for live list
fun getLiveDataObserver(): MutableLiveData<MutableList<String>> {
return liveDataDashIcons
}
// Observer for each icon
fun getLiveItemObserver(): MutableLiveData<String> {
return liveItemData
}
// Set icon list
fun setLiveDashIconsList(iconList: MutableLiveData<MutableList<String>>) {
liveDataDashIcons.value = iconList.value
}
// Set data for data
fun setItemData(icon : MutableLiveData<String>) {
liveItemData.value = icon.toString()
}
var iconList = mutableListOf<String>()
}
MainFragment:
private fun populateIconList() : MutableLiveData<MutableList> {
var iconList = viewModel.liveDataDashIcons
// Roster icon
if (roster_dash_layout.visibility == View.VISIBLE) {
iconList.value!!.add(getString(R.string.roster))
} else {
if (iconList.value!!.contains(getString(R.string.roster))) {
iconList.value!!.remove(getString(R.string.roster))
}
}
}
DialogFragment:
private fun setIconList(iconList: MutableList){
var iconList = viewModel.iconList
Log.d(TAG, "viewModel iconList = " + iconList)
if (iconList.contains(getString(R.string.roster))) {
binding.radioButtonRosterPick.setBackgroundResource(R.drawable.icon_helmet_blue_bg)
}
}

how to save and retrieve application info to shared preference in kotlin?

I want to hide app icon from grid view and save it.
I can save application info to shared preferences as mutable set string and get it but cant convert string to application info and show in my grid view
my app activity
private var applist: List<ApplicationInfo>? = null
private var listadaptor: ApplicationAdapter? = null
private var grid: GridView? = null
private var mSelected: ArrayList<Any> = ArrayList()
var context: Activity = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_apps)
val packageManager = getPackageManager()
LoadApplications().execute()
grid = findViewById<View>(R.id.grid) as GridView
grid!!.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE)
grid!!.adapter = listadaptor
grid!!.onItemClickListener = AdapterView.OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val app = applist!![position]
try {
val intent = packageManager.getLaunchIntentForPackage(app.packageName)
intent?.let { startActivity(it) }
} catch (e: Exception) {
Toast.makeText(this#appsActivity, e.message, Toast.LENGTH_LONG).show()
}
}
grid!!.onItemLongClickListener = AdapterView.OnItemLongClickListener { parent, view, position, id ->
val position1: String = (position).toString()
if (mSelected.contains(position1)) {
mSelected.remove(position1)
view.setBackgroundColor(Color.TRANSPARENT) // remove item from list
// update view (v) state here
// eg: remove highlight
} else {
mSelected.add(position1)
view.setBackgroundColor(Color.LTGRAY) // add item to list
// update view (v) state here
// eg: add highlight
}
button3.setOnClickListener(
object : View.OnClickListener {
override fun onClick(view: View?) {
val builder1: AlertDialog.Builder = AlertDialog.Builder(this#appsActivity)
builder1.setMessage("Are you sure you want to delete it ?")
builder1.setCancelable(true)
builder1.setPositiveButton(
"Yes",
DialogInterface.OnClickListener { dialog, id ->
deleteSelectedItems()
mSelected.remove(position)
listadaptor!!.notifyDataSetChanged()
val app = applist!![position]
listadaptor!!.remove(app)
})
builder1.setNegativeButton(
"No",
DialogInterface.OnClickListener { dialog, id -> dialog.cancel() })
val alert11: AlertDialog = builder1.create()
alert11.show()
}
})
true
}
}
private fun deleteSelectedItems() {
val checked: SparseBooleanArray = grid!!.getCheckedItemPositions()
if (checked != null) {
val list: List<Any> = mSelected
for (i in 0 until checked.size()) {
if (checked.valueAt(i)) {
mSelected.remove(checked.keyAt(i))
}
}
}
}
private fun checkForLaunchIntent(list: List<ApplicationInfo>): List<ApplicationInfo> {
val applist = ArrayList<ApplicationInfo>()
for (info in list) {
try {
if (null != packageManager!!.getLaunchIntentForPackage(info.packageName)) {
applist.add(info)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
Collections.sort(applist, ApplicationInfo.DisplayNameComparator(packageManager))
return applist
}
#SuppressLint("StaticFieldLeak")
private inner class LoadApplications : AsyncTask<Void?, Void?, Void?>() {
override fun doInBackground(vararg params: Void?): Void? {
applist = checkForLaunchIntent(packageManager!!.getInstalledApplications(
PackageManager.GET_META_DATA))
listadaptor = ApplicationAdapter(this#appsActivity,
R.layout.grid_item, applist!!)
return null
}
override fun onPostExecute(result: Void?) {
grid!!.adapter = listadaptor
super.onPostExecute(result)
}
}
items are deleted, but after re-running the application, all installed apps will be restored in gridview
You can look into my open source project LibTron which has one module library for SharedPref written in Kotlin.
To use the library follow the instruction in Project ReadMe
Example to use the library:
val applicationInfo: ApplicationInfo = sharedprefrence.Object(name = "sharedprefKey", defaultValue = null)
Or in case you want to use it without the help of the you can use GSON, Moshi, Jackson type libraries to convert to/from string to your ApplicationInfo class while saving or reading from the Sharedprefrence

How can I ensure Kotlin Coroutines are finishing in the correct order?

I am attempting to build a library that allows an app to download a json file I provide, and then based on its contents, download images from the web. I have implemented it thus far with Kotlin Coroutines along with Ktor, but I have an issue which is evading my grasp of what to do.
This is the data class I am using to define each image:
data class ListImage(val name: String, val url: String)
The user calls an init function which downloads the new json file. Once that file is downloaded, the app needs to download a number of images as defined by the file using getImages. Then a list is populated using the data class and an adapter.
Here is the code I am using to fetch the file:
fun init(context: Context, url: String): Boolean {
return runBlocking {
return#runBlocking fetchJsonData(context, url)
}
}
private suspend fun fetchJsonData(context: Context, url: String): Boolean {
return runBlocking {
val client: HttpClient(OkHttp) {
install(JsonFeature) {}
}
val data = async {
client.get<String>(url)
}
try {
val json = data.await()
withContext(Dispatchers.IO) {
context
.openFileOutput("imageFile.json", Context.MODE_PRIVATE)
.use { it.write(json.toByteArray()) }
}
} catch (e: Exception) {
return#runBlocking false
}
}
}
This works and gets the file written locally. Then I have to get the images based on the contents of the file.
suspend fun getImages(context: Context) {
val client = HttpClient(OkHttp)
// Gets the image list from the json file
val imageList = getImageList(context)
for (image in imageList) {
val imageName = image.name
val imageUrl = image.url
runBlocking {
client.downloadFile(context, imageName, imageUrl)
.collect { download ->
if (download == Downloader.Success) {
Log.e("SDK Image Downloader", "Successfully downloaded $imageName.")
} else {
Log.i("SDK Image Downloader", "Failed to download $imageName.")
}
}
}
}
}
private suspend fun HttpClient
.downloadFile(context: Context, fileName: String, url: String): Flow<Downloader> {
return flow {
val response = this#downloadFile.request<HttpResponse> {
url(url)
method = HttpMethod.Get
}
val data = ByteArray(response.contentLength()!!.toInt())
var offset = 0
do {
val currentRead = response.content.readAvailable(data, offset, data.size)
offset += currentRead
} while (currentRead > 0)
if (response.status.isSuccess()) {
withContext(Dispatchers.IO) {
val dataPath =
"${context.filesDir.absolutePath}${File.separator}${fileName}"
File(dataPath).writeBytes(data)
}
emit(Downloader.Success)
} else {
emit(Downloader.Error("Error downloading image $fileName"))
}
}
}
If the file is already on the device and I am not attempting to redownload it, this also works. The issue is when I try to get the file and then the images in order when the app is first run. Here is an example of how I am trying to call it:
lateinit var loaded: Deferred<Boolean>
lateinit var imagesLoaded: Deferred<Unit>
#InternalCoroutinesApi
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
val context: Context = this
loaded = GlobalScope.async(Dispatchers.Default) {
init(context)
}
GlobalScope.launch { loaded.await() }
imagesLoaded = GlobalScope.async(Dispatchers.Default) {
getDeviceImages(context)
}
GlobalScope.launch { imagesLoaded.await() }
configureImageList(getImageList(context))
}
fun configureImageList(imageList: MutableList<Image>) {
val imageListAdapter = ImageListAdapter(this, imageList)
with(image_list) {
layoutManager = LinearLayoutManager(context)
setHasFixedSize(true)
itemAnimator = null
adapter = imageListAdapter
}
}
This falls apart. So the two scenarios that play out are:
I run this code as-is: The file is downloaded, and ~75% of the images are downloaded before the app crashes with a java.io.IOException: unexpected end of stream on the url. So it seems that the images are starting to download before the file is fully written.
I run the app once without the image code. The file is downloaded. I comment out the file downloading code, and uncomment out the image downloading code. The images are downloaded, the app works as I want. This suggests to me that it would work if the first coroutine was actually finished before the second one started.
I have written and rewritten this code as many ways as I could think of, but I cannot get it to run without incident with both the file writing and image downloading completing successfully.
What am I doing wrong in trying to get these coroutines to complete consecutively?
I just figured it out after stumbling upon this question. It appears as though my HttpClient objects were sharing a connection instead of creating new ones, and when the server closed the connection it caused in-flight operations to unexpectedly end. So the solution is:
val client = HttpClient(OkHttp) {
defaultRequest {
header("Connection", "close")
}
}
I added this to each of the Ktor client calls and it now works as intended.

Kotlin read/import Map from txt.file

I am working on a learning project, that is supposed to import and export Flashcards to a txt.file which is later supposed to be used in questioning.
Currently, I am stuck on the import part and the research I did is not really working, because I am not really getting it.
I have this overall map where I save the term: definition in a map
private var flashCardMap = mutableMapOf<String, String>()
Then I have this export function
private fun export() {
println("File name:")
scan.nextLine()
val fileName = scan.nextLine()
val myFile = File(fileName)
try {
myFile.bufferedWriter().use { out->
flashCardMap.forEach {
out.write("${it.key}:${it.value}\n")
} }
println("${flashCardMap.size} cards have been saved.")
} catch (e: FileNotFoundException) {
println("File not Found.")
}
}
which exports all the cards I defined earlier in a txt. file like this with one or more Flashcard (Card = Definition)
key:value
AND here is where I am stuck. I try to import a .txt file and the including map but it does not work. It Is supposed to import the map and tell me how many Cards where imported and the add them to my current flashcardmap with which I am working in this session. Here is what I tried:
private fun import() {
println("File name:")
scan.nextLine()
val fileName = scan.nextLine()
val myFile = File("$fileName")
try {
val importMap =
myFile.readLines().chunked(2) {
it[0] to it[1]
}.toMap()
println("${importMap.size} cards have been loaded.")
flashCardMap.putAll(importMap)
} catch (e: FileNotFoundException) {
println("File not Found.")
}
}
There's actually so many ways to serialize structured data to a file, but since your example uses the key:value format, separated by new-lines, we'll stick with that.
This class should suit your needs. But it's pretty simplistic, and lacks any sort of error handling.
class Serializer(private val filePath: Path, private val delimiter: String = ":") {
fun export(data: Map<String, String>) {
filePath.toFile().writer().use { writer ->
for ((key, value) in data) {
writer.write("$key$delimiter$value\n")
}
}
}
fun import(): Map<String, String> {
val data = mutableMapOf<String, String>()
filePath.toFile().reader().use { reader ->
reader.forEachLine { line ->
val (key, value) = line.split(delimiter)
data[key] = value
}
}
return data
}
}
If you want to leverage a mature format, then the built-in java.util.Properties class can make things even easier. The only gotcha is that it uses a = delimiter by default, but it's supposed to be able to read a : delimiter too.
class PropertiesSerializer(private val filePath: Path) {
fun export(data: Map<String, String>) {
val props = Properties()
for ((key, value) in data) {
props[key] = value
}
filePath.toFile().outputStream().use { stream ->
props.store(stream, null)
}
}
fun import(): Map<String, String> {
val props = Properties()
filePath.toFile().inputStream().use { stream ->
props.load(stream)
}
return props
.map { (key, value) -> key.toString() to value.toString() }
.toMap()
}
}

Constructor visibility restricted to file

I want to create an easier way to handle SharedPreferences.
The way I want to call it is like this
get preference:
val email = SharedPrefs.userdata.email
val wifiOnly = SharedPrefs.connections.wifiOnly
set preference:
SharedPrefs.userdata.email = "someone#example.com"
SharedPrefs.connections.wifiOnly = true
I'm able to do so like this:
App.instance returns a Context object in the following snippet
object SharedPrefs {
val userdata by lazy { UserPreferences() }
val connections by lazy { ConnectionPreferences() }
class UserPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
class ConnectionPreferences {
private val prefs: SharedPreferences = App.instance.getSharedPreferences("connections", Context.MODE_PRIVATE)
var wifyOnly: Boolean
get() = prefs.getBoolean("wifiOnly", false)
set(value) = prefs.edit().putBoolean("wifyOnly", value).apply()
}
}
The problem is that this can still be called: SharedPrefs.UserPreferences()
Can I make this constructor private to this file or object only?
You can separate the interface and the implementation class, and make the latter private to the object:
object SharedPrefs {
val userdata: UserPreferences by lazy { UserPreferencesImpl() }
interface UserPreferences {
var email: String
}
private class UserPreferencesImpl : UserPreferences {
private val prefs: SharedPreferences =
App.instance.getSharedPreferences("userdata", Context.MODE_PRIVATE)
override var email: String
get() = prefs.getString("email", null)
set(value) = prefs.edit().putString("email", value).apply()
}
// ...
}
Alternatively, if you are developing a library or you have a modular architecture, you can make use of the internal visibility modifier to restrict the visibility to the module:
class UserPreferences internal constructor() { /* ... */ }
You can try something like this
class UserPreferences private constructor()
{
// your impl
}
This is the reference