WorkManager observe retrofit progress - kotlin

I am uploading the file using Kotlin workmanager. In CoroutineWorkmanager, I do file upload with a suspend upload function. I want to observe retrofit progress and show it on ui. I can see the retrofit progress state but I cannot observe it in workManager.
My request body class where I can see the retrofit progress state :
class ProgressRequestBody : RequestBody {
val mFile: File
val ignoreFirstNumberOfWriteToCalls : Int
constructor(mFile: File) : super(){
this.mFile = mFile
ignoreFirstNumberOfWriteToCalls = 0
}
constructor(mFile: File, ignoreFirstNumberOfWriteToCalls : Int) : super(){
this.mFile = mFile
this.ignoreFirstNumberOfWriteToCalls = ignoreFirstNumberOfWriteToCalls
}
var numWriteToCalls = 0
private val _shared = MutableStateFlow<Float>(0F)
val shared : StateFlow<Float> = _shared
override fun contentType(): MediaType? {
return "image/*".toMediaTypeOrNull()
}
#Throws(IOException::class)
override fun contentLength(): Long {
return mFile.length()
}
#Throws(IOException::class)
override fun writeTo(sink: BufferedSink) {
numWriteToCalls++
val fileLength = mFile.length()
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
val `in` = FileInputStream(mFile)
var uploaded: Long = 0
try {
var read: Int
var lastProgressPercentUpdate = 0.0f
read = `in`.read(buffer)
while (read != -1) {
uploaded += read.toLong()
sink.write(buffer, 0, read)
read = `in`.read(buffer)
if (numWriteToCalls > ignoreFirstNumberOfWriteToCalls ) {
val progress = (uploaded.toFloat() / fileLength.toFloat()) * 100f
if (progress - lastProgressPercentUpdate > 1 || progress == 100f) {
_shared.value = progress
Log.d("progress", "${shared.value}")
lastProgressPercentUpdate = progress
}
}
}
} finally {
`in`.close()
}
}
companion object {
private val DEFAULT_BUFFER_SIZE = 2048
}
}
The worker I uploaded the file to:
class UploadWorker #WorkerInject constructor(
private val repository: Repository,
#Assisted context: Context,
#Assisted params: WorkerParameters
): CoroutineWorker(context, params) {
private lateinit var result: UploadResult
#ObsoleteCoroutinesApi
#OptIn(ExperimentalCoroutinesApi::class)
#SuppressLint("RestrictedApi")
override suspend fun doWork(): Result {
return try{
val requestBody = ProgressRequestBody(File(fileUri!!.toUri().path))
val multipartBody = prepareBody(fileUri!!.toUri(), photoPart)
progressState(requestBody)
upload(multipartBody)
Result.Success()
}catch(e :Exception){
Result.failure()
}
}
private fun prepareBody( ): MultipartBody.Part {
return MultipartBody.Part.createFormData("photo", "photo", "image/*")
}
suspend fun upload(
multipartBody: MultipartBody.Part,
) {
repository.uploadPhotos(
multipartBody
).collect{ result ->
if (result is Result.Success) {
this.result = result.data
}
}
}
private suspend fun progressState(photoPart: ProgressRequestBody) {
coroutineScope {
launch {
photoPart.shared.collect{
setProgress(workDataOf(PROGRESS to it))
}
}
}
}
}
While this way I can't run the worker. I am getting the following error from the worker:
java.util.concurrent.CancellationException: Task was cancelled.
at androidx.work.impl.utils.futures.AbstractFuture.cancellationExceptionWithCause(AbstractFuture.java:1184)
at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:514)

Related

How to save text file in a document directory with Kotlin while using Jetpack with the same format? [duplicate]

I wrote an App, in Kotlin with Android Studio that write some strings to a file.
All work, I can write and read inside the App, but I can't see the file looking in Documents folder.
How can I use the folder Documents as a storage space?
Thank you
These are the function I use:
fun saveTextFile(view: View, nomeFile: String, testo: String, contesto: Context){
var fileStream: FileOutputStream
try {
fileStream = contesto.openFileOutput(nomeFile, MODE_APPEND) // OK esegue append
fileStream.write(testo.toByteArray())
fileStream.close()
}catch (e: Exception){
e.printStackTrace()
}
}
fun readTextFile(view: View, nomeFile: String, contesto: Context): String{
var fileInputStream: FileInputStream? = null
fileInputStream = contesto.openFileInput(nomeFile)
var inputStreamReader: InputStreamReader = InputStreamReader(fileInputStream)
val bufferedReader: BufferedReader = BufferedReader(inputStreamReader)
val stringBuilder: StringBuilder = StringBuilder()
var text: String? = null
while ({ text = bufferedReader.readLine(); text }() != null) {
stringBuilder.append(text)
}
inputStreamReader.close();
return(stringBuilder.toString())
}
Thank you, Livio
For writing in Documents folder of your device , you just need to make use of MediaStore for the same. You can take input for this function anything that you want like String , bitmap , PdfDocument and other's too .
For Your UseCase you can do the following ,
Global Variable :
private var imageUri: Uri? = null
override suspend fun saveDocument(context : Context, text : String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val date = System.currentTimeMillis()
val fileName = "$date.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}")
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
}
val imageUri = context.contentResolver.insert(collection, contentValues)
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "w").use { out -> out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
For Updating the already existing file , do the following . After creating file for the first time I have saved the imageUri in a global variable (If you want to store it permanently / or for a while you can use Jetpack Datastore / Shared Preference to save the same ):
suspend fun updateData(context: Context,text : String){
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "wa").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
For Reading the File , Do the following :
suspend fun read(context: Context, source: Uri): String = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openInputStream(source)?.use { stream -> stream.readText() }
?: throw IllegalStateException("could not open $source")
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
This is how the final code looks like :
class MainActivity : AppCompatActivity() {
private lateinit var btn: Button
private var imageUri: Uri? = null
private lateinit var btn2: Button
private lateinit var btn3 : Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn = findViewById(R.id.btnAdd)
btn2 = findViewById(R.id.getText)
btn3 = findViewById(R.id.updateText)
btn.setOnClickListener {
lifecycleScope.launch {
saveDocument(applicationContext, "Original ")
}
}
btn3.setOnClickListener {
lifecycleScope.launch {
updateData(applicationContext,"Appended")
}
}
btn2.setOnClickListener {
lifecycleScope.launch {
imageUri?.let { it1 ->
val data = read(applicationContext, it1)
Toast.makeText(applicationContext, "The data is $data ", Toast.LENGTH_LONG)
.show()
}
}
}
}
suspend fun saveDocument(context: Context, text: String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val date = System.currentTimeMillis()
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
imageUri = context.contentResolver.insert(collection, contentValues)
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "w").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
suspend fun updateData(context: Context, text: String) {
withContext(Dispatchers.IO) {
try {
val collection =
MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val dirDest = File(
Environment.DIRECTORY_DOCUMENTS,
context.getString(R.string.app_name)
)
val fileName = "test.txt"
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"$dirDest${File.separator}"
)
put(MediaStore.Files.FileColumns.IS_PENDING, 1)
}
withContext(Dispatchers.IO) {
imageUri?.let { uri ->
context.contentResolver.openOutputStream(uri, "wa").use { out ->
out?.write(text.toByteArray())
}
contentValues.clear()
contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}
}
} catch (e: FileNotFoundException) {
null
}
}
}
suspend fun read(context: Context, source: Uri): String = withContext(Dispatchers.IO) {
val resolver: ContentResolver = context.contentResolver
resolver.openInputStream(source)?.use { stream -> stream.readText() }
?: throw IllegalStateException("could not open $source")
}
private fun InputStream.readText(charset: Charset = Charsets.UTF_8): String =
readBytes().toString(charset)
I have three buttons . With the first I create a file , then the uri gets stored in the global variable . Then onClick of second button I add to the already existing file and then read the file using the third button using the same imageUri stored in the global variable
This is the demo for the same . Check when the buttons are being pressed and the output in the form of Toast at the bottom .

Observing live data from an API is not updating ui when data changes

I am trying to develop a football app demo. Data comes from an API from the api
It loads data as expected when app started, but when score of match changes, ui is not updating for scores by itself. I am using DiffUtil getChangePayload() to detect changes in score and status fields of Match objects which comes from the response. But it is not triggering when live match data changes. What am i missing?
P.S. I put layout in SwipeRefreshLayout and when i refresh, it gets scores and update the ui. But i want to see the match status and scores updating by itself.
Here is my code:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday: MutableLiveData<List<Matche>> = MutableLiveData()
init {
getMatchesToday()
}
fun getMatchesToday() = viewModelScope.launch {
safeMatchesToday()
}
private suspend fun safeMatchesToday() {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
matchesToday.postValue(it.matches)
}
}
}
}
}
class MatchesTodayFragment : Fragment() {
private var _binding: FragmentMatchesTodayBinding? =null
private val binding get() = _binding!!
private lateinit var mMatchesAdapter: MatchesAdapter
private val viewModel: MatchesViewModel by viewModels {
MatchesViewModelFactory(requireActivity().application, (requireActivity().application as MatchesApplication).repository)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupRecyclerView()
viewModel.matchesToday.observe(viewLifecycleOwner) { matches ->
mMatchesAdapter.differ.submitList(matches)
}
binding.srlMatchesToday.setOnRefreshListener {
viewModel.getMatchesToday()
binding.srlMatchesToday.isRefreshing = false
}
}
}
class MatchesAdapter(val fragment: Fragment): RecyclerView.Adapter<MatchesAdapter.ViewHolder>() {
private val differCallback = object: DiffUtil.ItemCallback<Matche>() {
override fun areItemsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Matche, newItem: Matche): Boolean {
return oldItem.status == newItem.status &&
oldItem.score.fullTime.home == newItem.score.fullTime.home &&
oldItem.score.fullTime.away == newItem.score.fullTime.away &&
oldItem == newItem
}
override fun getChangePayload(oldItem: Matche, newItem: Matche): Any? {
val bundle: Bundle = bundleOf()
if (oldItem.status != newItem.status) {
bundle.apply {
putString(Constants.MATCH_STATUS, newItem.status)
}
}
if (oldItem.score.fullTime.home != newItem.score.fullTime.home) {
bundle.apply {
putInt(Constants.HOME_SCORE, newItem.score.fullTime.home)
}
}
if (oldItem.score.fullTime.away != newItem.score.fullTime.away) {
bundle.apply {
putInt(Constants.AWAY_SCORE, newItem.score.fullTime.away)
}
}
if (bundle.size() == 0) {
return null
}
return bundle
}
}
val differ = AsyncListDiffer(this, differCallback)
#SuppressLint("UseCompatLoadingForDrawables")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val match = differ.currentList[position]
holder.apply {
Glide.with(fragment)
.load(match.homeTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivHomeTeamImage)
Glide.with(fragment)
.load(match.awayTeam.crest)
.placeholder(fragment.resources.getDrawable(R.drawable.ic_ball))
.into(ivAwayTeamImage)
tvHomeTeamName.text = match.homeTeam.name
tvAwayTeamName.text = match.awayTeam.name
when (match.status) {
Constants.TIMED -> {
tvMatchTime.text = Constants.toTimeForTR(match.utcDate)
tvHomeTeamScore.text = "-"
tvAwayTeamScore.text = "-"
}
Constants.PAUSED -> {
tvMatchTime.text = Constants.FIRST_HALF
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
Constants.FINISHED -> {
tvMatchTime.text = Constants.FINISHED
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
else -> {
tvMatchTime.text = Constants.IN_PLAY
tvHomeTeamScore.text = match.score.fullTime.home.toString()
tvAwayTeamScore.text = match.score.fullTime.away.toString()
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val item = payloads[0] as Bundle
val status = item.getString(Constants.MATCH_STATUS)
val homeScore = item.getInt(Constants.HOME_SCORE)
val awayScore = item.getInt(Constants.AWAY_SCORE)
holder.apply {
tvMatchTime.text = status
tvHomeTeamScore.text = homeScore.toString()
tvAwayTeamScore.text = awayScore.toString()
Log.e("fuck", status.toString())
}
}
super.onBindViewHolder(holder, position, payloads)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
}
LiveData only pushes new values if you command it to. Since you want to do it repeatedly, you need to create a loop. This is very easy to do using the liveData coroutine builder.
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
val response = repository.getMatchesToday()
if (response.isSuccessful) {
response.body()?.let {
emit(it.matches)
}
}
}
delay(5000) // however many ms you want between fetches
}
}
}
If this is a Retrofit response, I think checking isSuccessful is redundant because body() will be non-null if and only if isSuccessful is true. So it could be simplified a bit from what you have:
class MatchesViewModel(
app: Application,
private val repository: MatchesRepository
): AndroidViewModel(app) {
val matchesToday = liveData {
while (true) {
if (Constants.checkConnection(this)) {
repository.getMatchesToday()?.body()?.matches?.let(::emit)
}
delay(5000) // however many ms you want between fetches
}
}
}

While loop doesn't seem to work with .putFile when uploading multiple images to Firebase storage in Kotlin

I have been trying to upload multiple images to Firebase Storage. But, I am not able to do it successfully. I could successfully upload the image (single) to the storage and add the URL of the image to the Firestore, now that I revised my code to upload up to five images, it could be any number of images from 1 to 5.
R.id.btn_submit -> {
if (validateDetails()) {
uploadImage()
}
}
The above code, calls the following function after validating the fields, which then calls the function uploadImageToCloudStorage. mSelectedImageFileUriList is private var mSelectedImageFileUriList: MutableList<Uri?>? = null. It all seems to work correctly.
private fun uploadImage() {
showProgressDialog(resources.getString(R.string.please_wait))
FirestoreClass().uploadImageToCloudStorage(
this#AddProductActivity,
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' }
)
}
Following code is where I guess is a mistake.
fun uploadImageToCloudStorage(
activity: AddProductActivity,
imageFileURI: MutableList<Uri?>?,
imageType: String,
directoryName: String,
title: String
) {
var i = 0
val imageURLList = ArrayList<String>()
val itr = imageFileURI?.iterator()
if (itr != null) {
while (itr.hasNext()) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(
"/$directoryName/" + imageType + "." + Constants.getFileExtension(
activity,
imageFileURI[i]
)
)
sRef.putFile(imageFileURI[i]!!)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
if (i < imageFileURI.size) {
i += 1
imageURLList.add(uri.toString())
} else {
activity.imageUploadSuccess(imageURLList)
}
}
}
.addOnFailureListener { exception ->
activity.hideProgressDialog()
Log.e(
activity.javaClass.simpleName,
exception.message,
exception
)
}
}
} else {
Toast.makeText(
activity,
"There is no images in the ArrayList of URI",
Toast.LENGTH_SHORT
).show()
}
}
EDIT: After receiving the first answer.
I have created a QueueSyn.kt file and added the code in the Answer. The activity where the images and the button are changed to
class AddProductActivity : BaseActivity(), View.OnClickListener, QueueSyncCallback {
The following function is called when the button is hit.
private fun uploadProductImage() {
showProgressDialog(resources.getString(R.string.please_wait))
QueueSync(
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' },
this
).startUploading()
}
I have also implemented these two methods in the class AddProductActivity, but I don't know what should go inside this.
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
TODO("Not yet implemented")
}
override fun getFileExtension(uri: Uri): String {
TODO("Not yet implemented")
}
Error:
This should work
import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import java.util.*
import kotlin.collections.ArrayList
interface QueueSyncCallback {
fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>)
fun getFileExtension(uri: Uri): String
}
class QueueSync(
imageFileURI: MutableList<Uri?>?,
private val imageType: String,
private val directoryName: String,
private val title: String,
private val callback: QueueSyncCallback,
private val maxActive: Int = 5
) {
private val queue: LinkedList<Uri> = LinkedList()
private val runningQueue: MutableList<Uri> = Collections.synchronizedList(
object : ArrayList<Uri>() {
override fun remove(element: Uri): Boolean {
val removed = super.remove(element)
if (isEmpty() && queue.isEmpty()) {
callback.completed(successList, failureList)
} else if (queue.isNotEmpty()) {
addToRunningQueue()
}
return removed
}
}
)
private val successList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
private val failureList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
init {
if (imageFileURI != null)
for (uri in imageFileURI) {
if (uri != null)
queue.add(uri)
}
}
private fun getLocation(uri: Uri) = "/$directoryName/$imageType.${callback.getFileExtension(uri)}"
fun startUploading() {
var i = 0
if (queue.isEmpty()) {
callback.completed(successList, failureList)
return
}
while (i < maxActive && queue.isNotEmpty()) {
addToRunningQueue()
i++
}
}
private fun addToRunningQueue() {
val uri = queue.poll()!!
runningQueue.add(uri)
uploadImageToCloudStorage(uri)
}
private fun uploadImageToCloudStorage(locationUri: Uri) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(getLocation(locationUri))
sRef.putFile(locationUri)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
successList.add(uri)
runningQueue.remove(locationUri)
}
}
.addOnFailureListener {
failureList.add(locationUri)
runningQueue.remove(locationUri)
}
}
}
Since your need requires usage of threads so to prevent race conditions I had to use Collections.synchronizedList. To use this you need to implement QueueSyncCallback in your activity and pass it as a reference to QueueSync. Make sure that any piece of code written inside completed is wrapped inside runOnMainThread if it is going to access views in any way since completed will not run on main thread as far as I know. This should work however I am not able to test it since it is based on your current code.
Edit:- Answering after edit
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
imageUploadSuccess(successList)
hideProgressDialog()
}
override fun getFileExtension(uri: Uri): String {
Constants.getFileExtension(this, imageFileURI[i])
}

Item on recycler-view disappear after changing to a different activity and back again

A very newbie programmer here and not a good English typer. Im trying to create a checker for purchase that already made previous using the PurchaseHistoryResponseListener. And When a checker found something, it will add to a list and then feed the recyclerview_MYBook with that data. The issue is that when launching the app, the data is flow through the recyclerview_MYBook perfectly, but when moving to different activity and going back to the previous activity through a different method (button click) the data on the recyclerview_MYBook doesn't show up, only through a conventional back button, the data on the recyclerview show up. Below here is my noob code
class MainActivity : AppCompatActivity(), PurchasesUpdatedListener {
private lateinit var billingClient: BillingClient
private lateinit var blogadapternew: BlogRecyclerAdapterNew
private lateinit var blogadapterpremium: BlogRecyclerAdapterPremium
private lateinit var blogadapterfree: BlogRecyclerAdapterFree
private lateinit var blogadaptermybook: BlogRecyclerAdapterMyBook
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
auth = FirebaseAuth.getInstance()
//FirebaseAuth.getInstance().signOut()
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
setContentView(R.layout.activity_main)
recycler_viewNew.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL,false)
recycler_viewNew.adapter= BlogRecyclerAdapterNew()
recycler_viewPremium.layoutManager = LinearLayoutManager(this,RecyclerView.HORIZONTAL,false)
recycler_viewPremium.adapter= BlogRecyclerAdapterPremium()
recycler_viewFree.layoutManager = LinearLayoutManager(this,RecyclerView.HORIZONTAL,false)
recycler_viewFree.adapter= BlogRecyclerAdapterFree()
recycler_viewMyBook.layoutManager = LinearLayoutManager(this,RecyclerView.HORIZONTAL,false)
recycler_viewMyBook.adapter= BlogRecyclerAdapterMyBook()
if (supportActionBar != null)
supportActionBar?.hide()
setupBillingClient()
initrecyclerView()
initrecyclerViewPremium()
initrecyclerViewFree()
initrecyclerViewMyBook()
addDataSetNew()
addDataSetPremium()
addDataSetFree()
Logo.setOnClickListener{
val intent = Intent(MonstaLogo.context, MainActivity::class.java)
MonstaLogo.context.startActivity(intent)
}
MainFeaturedButton.setOnClickListener {
val intent = Intent(MainFeaturedButton.context, MainActivity::class.java)
MainFeaturedButton.context.startActivity(intent)
}
MainNewButton.setOnClickListener {
val intent = Intent(MainNewButton.context, NewActivity::class.java)
MainNewButton.context.startActivity(intent)
}
NewMore.setOnClickListener{
val intent = Intent(NewMore.context, NewActivity::class.java)
NewMore.context.startActivity(intent)
}
MainPremiumButton.setOnClickListener {
val intent = Intent(MainPremiumButton.context, PremiumActivity::class.java)
MainPremiumButton.context.startActivity(intent)
}
PremiumMore.setOnClickListener{
val intent = Intent(PremiumMore.context, PremiumActivity::class.java)
PremiumMore.context.startActivity(intent)
}
MainFreeButton.setOnClickListener {
val intent = Intent(MainFreeButton.context, FreeActivity::class.java)
MainFreeButton.context.startActivity(intent)
}
FreeMore.setOnClickListener {
val intent = Intent(FreeMore.context, FreeActivity::class.java)
FreeMore.context.startActivity(intent)
}
MainMyBookButton.setOnClickListener {
val intent = Intent(MainMyBookButton.context, MyBookActivity::class.java)
MainMyBookButton.context.startActivity(intent)
}
MyBookMore.setOnClickListener {
val intent = Intent(MyBookMore.context, MyBookActivity::class.java)
MyBookMore.context.startActivity(intent)
}
}
private fun setupBillingClient() {
billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases()
.setListener(this)
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
println("Setup Billing Done")
PurchaseHistoryResponseListener()
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
println("Failed")
setupBillingClient()
println("Restart Connection")
}
})
}
override fun onPurchasesUpdated(
billingResult: BillingResult?,
purchases: MutableList<Purchase>?
) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private fun PurchaseHistoryResponseListener (){
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) {
responseCode, result ->
// println("queryPurchasesAsync INAPP results: ${result?.size}")
// println("Getting Purchase History")
println("$result")
val dataMyBook1 = DataSourceMyBook.createDataSet()
if ("testcode1" in result.toString()) {
println("found it 1")
dataMyBook1.add((BlogPost( "BookName","Link","No")))
}
if ("testcode2" in result.toString()) {
println("found it 2")
dataMyBook1.add((BlogPost( "BookName","Link","No")))
}
if ("testcode3" in result.toString()) {
println("found it 3")
dataMyBook1.add((BlogPost( "BookName","Link","No")))
}
blogadaptermybook.submitList(dataMyBook1)
println(dataMyBook1)
}
}
private fun addDataSetNew(){
val dataNew = DataSourceNew.createDataSet()
blogadapternew.submitList(dataNew)
}
private fun addDataSetPremium(){
val dataPremium = DataSourcePremium.createDataSet()
blogadapterpremium.submitList(dataPremium)
}
private fun addDataSetFree(){
val dataFree = DataSourceFree.createDataSet()
blogadapterfree.submitList(dataFree)
}
/*private fun addDataSetMyBook(){
val dataMyBook1 = DataSourceMyBook.createDataSet()
blogadaptermybook.submitList(dataMyBook1)
}*/
/*private fun addDataSetMyBook(){
val dataMyBook1 = DataSourceMyBook.createDataSet()
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) {
responseCode, result ->
println("$result")
if ("bbbg_s2_c1_testcode1" in result.toString()){
dataMyBook1.add((BlogPost( "Mini Comic 1","Link","No")))
}
if ("bbbg_s2_c1_testcode2" in result.toString()){
dataMyBook1.add((BlogPost( "Mini Comic 2","Link","No")))
}
if ("bbbg_s2_c1_testcode3" in result.toString()){
dataMyBook1.add((BlogPost( "Mini Comic 3","Link","No")))
}
blogadaptermybook.submitList(dataMyBook1)
}}*/
/*dataMyBook.add((BlogPost( "Mini Comic 1","Link","No")))
dataMyBook.add((BlogPost( "Mini Comic 1","Link","No")))
dataMyBook.add((BlogPost( "Mini Comic 1","Link","No")))*/
private fun initrecyclerView(){
recycler_viewNew.apply {
layoutManager = LinearLayoutManager(this#MainActivity,RecyclerView.HORIZONTAL,false)
val topSpacingItemDecoration = TopSpacingItemDecoration(padding = 30)
addItemDecoration(topSpacingItemDecoration)
blogadapternew = BlogRecyclerAdapterNew()
adapter = blogadapternew
}
}
private fun initrecyclerViewPremium(){
recycler_viewPremium.apply {
layoutManager = LinearLayoutManager(this#MainActivity,RecyclerView.HORIZONTAL,false)
val topSpacingItemDecoration = TopSpacingItemDecoration(padding = 30)
addItemDecoration(topSpacingItemDecoration)
blogadapterpremium = BlogRecyclerAdapterPremium()
adapter = blogadapterpremium
}
}
private fun initrecyclerViewFree(){
recycler_viewFree.apply {
layoutManager = LinearLayoutManager(this#MainActivity,RecyclerView.HORIZONTAL,false)
val topSpacingItemDecoration = TopSpacingItemDecoration(padding = 30)
addItemDecoration(topSpacingItemDecoration)
blogadapterfree = BlogRecyclerAdapterFree()
adapter = blogadapterfree
}
}
private fun initrecyclerViewMyBook(){
recycler_viewMyBook.apply {
layoutManager =
LinearLayoutManager(this#MainActivity, RecyclerView.HORIZONTAL, false)
val topSpacingItemDecoration = TopSpacingItemDecoration(padding = 30)
addItemDecoration(topSpacingItemDecoration)
blogadaptermybook = BlogRecyclerAdapterMyBook()
adapter = blogadaptermybook
}
}
public override fun onStart() {
super.onStart()
val currentUser = auth.currentUser
updateUI(currentUser)
}
private fun updateUI(currentUser: FirebaseUser?) {
if (currentUser != null) {
AccountSettingButton.setImageResource(R.drawable.profileicon)
}
}
}
Here is adapter
class BlogRecyclerAdapterMyBook : RecyclerView.Adapter() {
private var items: List<BlogPost> = ArrayList()
private var items2: List<BlogPost> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return BlogViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.layout_blog_list_item_mybook,
parent,
false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is BlogViewHolder -> {
holder.bind(items.get(position))
holder.bind(items2.get(position))
}
}
}
override fun getItemCount(): Int {
return items.size
}
fun submitList(bloglist: List<BlogPost>) {
items = bloglist
items2 = bloglist
}
class BlogViewHolder constructor(itemView: View) : RecyclerView.ViewHolder(itemView) {
val blogImage: ImageButton = itemView.blog_imagemybook
val blogTitle: TextView = itemView.blog_titlemybook
val premiumImage: ImageView = itemView.premiumicon
fun bind(blogPost: BlogPost) {
blogTitle.setText(blogPost.title)
val requestOptions = RequestOptions()
.placeholder(R.drawable.mocksplash)
.error(R.drawable.disconnect)
Glide.with(itemView.blog_imagemybook)
.applyDefaultRequestOptions(requestOptions)
.load(blogPost.image)
.into(blogImage)
blogImage.setOnClickListener {
Toast.makeText(blogImage.context, "<<Swipe left<<", Toast.LENGTH_SHORT).show()
val intent = Intent(blogTitle.context, ComicReadingActivity::class.java)
var KomikName = blogTitle.text.toString()
intent.putExtra("KomikName",Name)
blogImage.context.startActivity(intent)
}
}
}
}
and here the data source file where that will store the data for the adapter
class DataSourceMyBook{
companion object{
fun createDataSet(): ArrayList<BlogPost> {
val dataMyBook1 = ArrayList<BlogPost>()
return dataMyBook1
}
}
}

Polling repository with coroutine channels

I'm playing with coroutine channels and I wanted to implemented a polling test project. The idea is that a viewmodel will listen for data from a repository that polls an endpoint repeatedly.
When I pass a coroutineScope to the repository, the polling works, however when I create a new coroutineSCope in the repository, I see the data being injected into the channel, but it's not received on the viewmodel.
So this works:
class PollingViewModel : ViewModel() {
val counter = MutableLiveData<String>().apply { value = "uninitialized" }
private val repository = Repository()
init {
viewModelScope.launch {
val channel = repository.poll(this /* scope */)
channel.consumeEach {
Log.d("foo", "Viewmodel received [$it]")
counter.postValue(it.toString())
}
}
}
}
class Repository {
private var startValue = 0
suspend fun poll(coroutineScope: CoroutineScope) =
coroutineScope.produce(capacity = Channel.CONFLATED) {
while (true) {
Log.d("foo", "Sending value [$startValue]")
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}
But this does not (viewmodel does not receive anything):
class PollingViewModel : ViewModel() {
val counter = MutableLiveData<String>().apply { value = "uninitialized" }
private val repository = Repository()
init {
viewModelScope.launch {
repository.poll().consumeEach {
Log.d("foo", "Viewmodel received [$it]")
counter.postValue(it.toString())
}
}
}
}
class Repository {
private var startValue = 0
suspend fun poll() = coroutineScope {
produce(capacity = Channel.CONFLATED) {
while (true) {
Log.d("foo", "Sending value [$startValue]")
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}
What is the issue with creating a coroutineScope at the repository level?
Looks like the solution is to create a new CoroutineContext in the repository:
class Repository {
private var startValue = 0
private val context: CoroutineContext by lazy(LazyThreadSafetyMode.NONE) {
Job() + Dispatchers.IO
}
suspend fun poll(): ReceiveChannel<Int> = coroutineScope {
produce(
context = context,
capacity = Channel.CONFLATED
) {
while (true) {
send(startValue++)
delay(POLLING_PERIOD_MILLIS)
}
}
}
companion object {
private const val POLLING_PERIOD_MILLIS = 1000L
}
}