MVVM Respository - kotlin

i have a error on code that says
overload resolution ambiguity. all these functions match
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() = movieDao.streamAll()
.onErrorResumeNext{
apiService.getMyMovie()
.doOnSuccess {
if (it.results.isEmpty()){
}else{
movieDao.deleteAll()
it.results.let {
Timber.d("input data")
val semuadata = it.map { data -> Movie.from(data) }
movieDao.insert(semuadata)
}
}
}
}
}
also there something like this in my error
enter image description here

You simply have to specify the parameter you take in onErrorResumeNext:
i have a error on code that says
overload resolution ambiguity. all these functions match
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() = movieDao.streamAll()
.onErrorResumeNext{ next: Publisher<List<Movie>> ->
apiService.getMyMovie()
.doOnSuccess {
if (it.results.isEmpty()){
}else{
movieDao.deleteAll()
it.results.let {
Timber.d("input data")
val semuadata = it.map { data -> Movie.from(data) }
movieDao.insert(semuadata)
}
}
}
}
}

Related

Error While Upload Image From Galley Using Drjacky Image Picker And Retrofit 2

hi guys I got an error like the one below when trying to upload an image from the galley:
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{efed650 15275:app.fadlyproject.com/u0a158} (pid=15275, uid=10158) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
the error comes from this line of code:
val parcelFileDescriptor =
contentResolver.openFileDescriptor(selectedImageUri!!, "r", null) ?: return
I'm using 3rd party libraries namely DrJacky and Retrofit 2. I've added some necessary things to the manifest as below:
Dependencies :
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.github.Drjacky:ImagePicker:2.3.19'
Manifest :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
image_view = findViewById(R.id.image_view)
button_upload = findViewById(R.id.button_upload)
image_view!!.setOnClickListener {
openImageChooser()
}
button_upload!!.setOnClickListener {
uploadImage()
}
}
private val profileLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
val uri = it.data?.data!!
selectedImageUri = uri
image_view!!.setImageURI(selectedImageUri)
} else parseError(it)
}
private fun openImageChooser() {
ImagePicker.with(this)
.provider(ImageProvider.BOTH)
.setDismissListener {
Log.d("ImagePicker", "onDismiss");
}
.createIntentFromDialog { profileLauncher.launch(it) }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_CODE_PICK_IMAGE -> {
selectedImageUri = data?.data
image_view!!.setImageURI(selectedImageUri)
}
}
}
}
private fun uploadImage() {
if (selectedImageUri == null) {
layout_root!!.snackbar("Select an Image First")
return
}
val parcelFileDescriptor =
contentResolver.openFileDescriptor(selectedImageUri!!, "r", null) ?: return
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
val file = File(cacheDir, contentResolver.getFileName(selectedImageUri!!))
val outputStream = FileOutputStream(file)
inputStream.copyTo(outputStream)
progress_bar!!.progress = 0
val body = UploadRequestBody(file, "image", this)
MyAPI().uploadImage(
MultipartBody.Part.createFormData(
"file",
file.name,
body
),
RequestBody.create(MediaType.parse("multipart/form-data"), "json")
).enqueue(object : Callback<UploadResponse> {
override fun onFailure(call: Call<UploadResponse>, t: Throwable) {
layout_root!!.snackbar(t.message!!)
progress_bar!!.progress = 0
}
override fun onResponse(
call: Call<UploadResponse>,
response: Response<UploadResponse>
) {
response.body()?.let {
layout_root!!.snackbar(it.message)
progress_bar!!.progress = 100
classes!!.text = it.data.classes
layout!!.visibility = View.VISIBLE
Glide.with(this#MainActivity).load("http://167.172.72.26:1337/"+ it.data.image_after_preprocessing).into(
image_view!!
)
}
}
})
}
override fun onProgressUpdate(percentage: Int) {
progress_bar!!.progress = percentage
}
companion object {
const val REQUEST_CODE_PICK_IMAGE = 101
}
This answer is late but it might help others.
I just added .crop() to it like this.
ImagePicker.with(this)
.crop()
.provider(ImageProvider.BOTH)
.setDismissListener {
Log.d("ImagePicker", "onDismiss");
}
.createIntentFromDialog { profileLauncher.launch(it) }
I noticed that the error only persist when using the gallery and adding the .crop() is the only solution to it.
In case you don't use Crop option, you just need to add:
mGalleryUri?.let { galleryUri ->
contentResolver.takePersistableUriPermission(
galleryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
https://developer.android.com/training/data-storage/shared/photopicker#persist-media-file-access
I'll change ACTION_GET_CONTENT to ACTION_OPEN_DOCUMENT on the next update.
[And if I could find a way to have both worlds(having crop and let developer use or not use takePersistableUriPermission, I'll update again.]

Kotlin .run scoped function - method 'void <init>()' not found

I have a problem with the following code snippet:
(Some functions' bodies are ommited for clear view)
fun collectLinks(page: Page): List<String> {
return LinksCrawler().run {
page.accept(this)
this.links
}
}
class LinksCrawler {
private var _links = mutableListOf<String>()
val links
get() = _links.toList()
fun visit(page: Page) { (...) }
fun visit(container: Container) = { (...) }
private fun visit(elements: List<HtmlElement>){ (...) }
}
When I invoke collectLinks() I get
Visitor$LinksCrawler: method 'void ()' not found
(where Visitor is my filename)
As far as I believe, problem would be caused by scope function .run(), maybe that it has no initialisation code that would do sth with LinksCrawler, but correct me if I am wrong.
I do it in .kts file, if it has any meaning. In overall, it is supposed to be an example for a Visitor design pattern. Full file code below:
import Visitor.HtmlElement.Image as Image
import Visitor.HtmlElement.Link as Link
import Visitor.HtmlElement.Table as Table
import Visitor.HtmlElement.Container as Container
main()
// ---------------
fun main() {
val page = Page(Container(Image(), Link(), Image()),
Table(),
Link(),
Container(Table(), Link()),
Container(Image(), Container(Image(), Link())))
println(collectLinks(page))
}
fun collectLinks(page: Page): List<String> {
return LinksCrawler().run {
page.accept(this)
this.links
}
}
class LinksCrawler {
private var _links = mutableListOf<String>()
val links
get() = _links.toList()
fun visit(page: Page) {
visit(page.elements)
}
fun visit(container: Container) = visit(container.elements)
private fun visit(elements: List<HtmlElement>){
for (e in elements) {
when (e) {
is Container -> e.accept(this)
is Link -> _links.add(e.href)
is Image -> _links.add(e.src)
else -> {}
}
}
}
}
fun Container.accept(feature: LinksCrawler) {
feature.visit(this)
}
fun Page.accept(feature: LinksCrawler) = feature.visit(this)
class Page(val elements: MutableList<HtmlElement> = mutableListOf()) {
constructor(vararg elements: HtmlElement) : this(mutableListOf()) {
for (s in elements) {
this.elements.add(s)
}
}
}
sealed class HtmlElement {
class Container(val elements: MutableList<HtmlElement> = mutableListOf()) : HtmlElement() {
constructor(vararg units: HtmlElement) : this(mutableListOf()) {
for (u in units) {
this.elements.add(u)
}
}
}
class Image : HtmlElement() {
val src: String
get() = "http://image"
}
class Link : HtmlElement() {
val href : String
get() = "http://link"
}
class Table : HtmlElement()
}

Cahining coroutines by using extension functions in Kotlin

I want to chain 3 coroutines by using Kotlin's extension functions. I know how to do it with regular ones, but can't manage it with extension functions. In fact, in the 2nd coroutine I can receive only one data sent from the 1st coroutine, but that's all. The program works but all I get on the console is Doc: 1st Document. What I'm doing wrong?
fun main(args: Array<String>) = runBlocking {
produceDocs().docLength().report().consumeEach {
println(it)
}
}
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength(): ReceiveChannel<Int> = coroutineScope {
val docsChannel: ReceiveChannel<String> = this#docLength
produce {
for (doc in docsChannel) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = coroutineScope {
val docLengthChannel: ReceiveChannel<Int> = this#report
produce {
for (len in docLengthChannel) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
}
You have to consume each channel independently in order to make emissions go through the chain, otherwise the first emission will never be consumed:
private fun CoroutineScope.produceDocs() = produce {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (this.isActive) {
val docs = getDocs()
for (doc in docs) {
send(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private suspend fun ReceiveChannel<String>.docLength() : ReceiveChannel<Int> = CoroutineScope(coroutineContext).produce {
for (doc in this#docLength) {
println("Doc: $doc") // OK. This works.
send(doc.count()) // ??? Not sure where this sends data to?
}
}
private suspend fun ReceiveChannel<Int>.report(): ReceiveChannel<String> = CoroutineScope(coroutineContext).produce {
for (len in this#report) {
println("Length: $len") // !!! Nothing arrived.
send("Report. Document contains $len characters.")
}
}
I suggest you a better approach to do the exact same thing using Flow:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = flow {
collect { doc ->
println("Doc: $doc")
emit(doc.count())
}
}
private fun Flow<Int>.report(): Flow<String> = flow {
collect { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
}
Or better like this:
private fun produceDocs(): Flow<String> = flow {
fun getDocs(): List<String> {
return listOf("1st Document", "2nd Newer Document")
}
while (true) {
val docs = getDocs()
for (doc in docs) {
emit(doc)
}
delay(TimeUnit.SECONDS.toMillis(2))
}
}
private fun Flow<String>.docLength(): Flow<Int> = transform { doc ->
println("Doc: $doc")
emit(doc.count())
}
private fun Flow<Int>.report(): Flow<String> = transform { len ->
println("Length: $len")
emit("Report. Document contains $len characters.")
}
And collect it like this:
produceDocs().docLength().report().collect {
println(it)
}
Or even better like this:
produceDocs()
.map { doc ->
println("Doc: $doc")
doc.count()
}
.map { len ->
println("Length: $len")
"Report. Document contains $len characters."
}
.collect {
println(it)
}

How to set up Viewmodel class properly?

I'm trying to follow some tutotial from github about MVVM model and i'm stuck at viewmodel class because there's an error says
Not enough information to infer type variable T
and
Type mismatch.
Required:Resource<Movie>
Found:Unit
And when i check my other class like ApiService, Dao, NetworkBoundResource, ApiResponse, Resources and respository everthing fine like this
ApiService :
interface ApiService {
#GET("3/movie/popular")
fun getMyMovie(#Query("api_key") api : String = "32bbbffe944d16d1d2a3ee46cfc6aaa0"
) : Flow<ApiResponse<MovieResponse.Movie>>
}
MovieDao:
#Dao
interface MovieDao : BaseDao<Movie> {
// #Insert(onConflict = OnConflictStrategy.REPLACE)
// fun insertMovie(movie: List<Movie>)
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun getMyMovie() : Flow<Movie>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun findAllMovie() : Maybe<List<Movie>>
#Query("SELECT * FROM `movie` ORDER by movie_id DESC")
fun streamAll() : Flowable<List<Movie>>
#Query("DELETE FROM `movie`")
fun deleteAll()
}
MovieRespository:
class MovieRespository (val apiService: ApiService, val movieDao: MovieDao) {
fun getListMovie() : Flow<Resource<Movie>> {
return networkBoundResource(
fetchFromLocal = { movieDao.getMyMovie() },
shouldFetchFromRemote = {true},
fetchFromRemote = {apiService.getMyMovie()},
processRemoteResponse = {},
saveRemoteData = {movieDao.insert(
it.results.let {
it.map { data -> Movie.from(data) }
}
)},
onFetchFailed = {_, _ ->}
).flowOn(Dispatchers.IO)
}
NeteorkBoundResource:
inline fun <DB, REMOTE> networkBoundResource(
crossinline fetchFromLocal: () -> Flow<DB>,
crossinline shouldFetchFromRemote: (DB?) -> Boolean = { true },
crossinline fetchFromRemote: () -> Flow<ApiResponse<REMOTE>>,
crossinline processRemoteResponse: (response: ApiSuccessResponse<REMOTE>) -> Unit = { Unit },
crossinline saveRemoteData: (REMOTE) -> Unit = { Unit },
crossinline onFetchFailed: (errorBody: String?, statusCode: Int) -> Unit = { _: String?, _: Int -> Unit }
) = flow<Resource<DB>> {
emit(Resource.Loading(null))
val localData = fetchFromLocal().first()
if (shouldFetchFromRemote(localData)) {
emit(Resource.Loading(localData))
fetchFromRemote().collect { apiResponse ->
when (apiResponse) {
is ApiSuccessResponse -> {
processRemoteResponse(apiResponse)
apiResponse.body?.let { saveRemoteData(it) }
emitAll(fetchFromLocal().map { dbData ->
Resource.Success(dbData)
})
}
is ApiErrorResponse -> {
onFetchFailed(apiResponse.errorMessage, apiResponse.statusCode)
emitAll(fetchFromLocal().map {
Resource.Error(
apiResponse.errorMessage,
it
)
})
}
}
}
} else {
emitAll(fetchFromLocal().map { Resource.Success(it) })
}
}
ApiResponse:
sealed class ApiResponse<T> {
companion object {
fun <T> create(error: Throwable): ApiErrorResponse<T> {
return ApiErrorResponse(
error.message ?: "Unknown error",
0
)
}
fun <T> create(response: Response<T>): ApiResponse<T> {
return if (response.isSuccessful) {
val body = response.body()
val headers = response.headers()
if (body == null || response.code() == 204) {
ApiEmptyResponse()
} else {
ApiSuccessResponse(
body,
headers
)
}
} else {
val msg = response.errorBody()?.string()
val errorMsg = if (msg.isNullOrEmpty()) {
response.message()
} else {
msg
}
ApiErrorResponse(
errorMsg ?: "Unknown error",
response.code()
)
}
}
}
}
/**
* separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
*/
class ApiEmptyResponse<T> : ApiResponse<T>()
data class ApiSuccessResponse<T>(
val body: T?,
val headers: okhttp3.Headers
) : ApiResponse<T>()
data class ApiErrorResponse<T>(val errorMessage: String, val statusCode: Int) : ApiResponse<T>()
Resource:
data class Resource<out T>(val status: Status, val data: T?, val message: String?) {
// data class Loading<T>(val loadingData: T?) : Resource<T>(Status.LOADING, loadingData, null)
// data class Success<T>(val successData: T?) : Resource<T>(Status.SUCCESS, successData, null)
// data class Error<T>(val msg: String, val error: T?) : Resource<T>(Status.ERROR, error, msg)
companion object {
fun <T> Success(data: T?): Resource<T> {
return Resource(Status.SUCCESS, data,null)
}
fun <T> Error(msg: String, data: T? = null): Resource<T> {
return Resource(Status.ERROR, data, msg)
}
fun <T> Loading(data: T? = null): Resource<T> {
return Resource(Status.LOADING, data, null)
}
}
}
MainViewModel:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Resource.Loading() ->{}
Resource.Success() ->{}
Resource.Error() ->{}
}
}.asLiveData(viewModelScope.coroutineContext)
}
to be specific this what the error looks like and this is the link of tutorial i learn
https://github.com/hadiyarajesh/flower
viewModel Class Error
You get the error, because Inside the when, you are trying to construct a new instance of Resource.Loading() etc, but those require a type, so it would need to be something like Resource.Loading<Movie>().
Tht being said, you are doing when(it.status), so the cases in the when, should not be a Resource.Loading, but Status.LOADING instead:
class MainViewModel(private val movieRespository: MovieRespository) : ViewModel() {
#ExperimentalCoroutinesApi
val getListMovies: LiveData<Resource<Movie>> = movieRespository.getListMovie().map {
when(it.status){
Status.LOADING ->{}
Status.SUCCESS ->{}
Status.ERROR ->{}
}
return#map it
}.asLiveData(viewModelScope.coroutineContext)
}
Also, since you are declaring LiveData<Resource<Movie>>, you need to return a Resource<Movie> from the map {} (we could drop return#map, it is just to be explicit)

Convert Observable of one type to another

I have the following code
class CurrencyRepository #Inject constructor(val apiInterface: ApiInterface,
val ratesDao: RatesDao) {
fun getRates(): Observable<List<Rates>> {
val observableFromApi = getCurrencyFromApi()
val observableFromDb = getRatesFromDb()
return Observable.concatArrayEager(observableFromApi , observableFromDb)
}
private fun getCurrencyFromApi(): Observable<Currency> {
return apiInterface.getRates()
.doOnNext {
Timber.i(it.toString())
val map = it.rates
val keys = map.keys
for (key in keys) {
ratesDao.insertRate(Rates(key , map.get(key)))
}
}
}
private fun getRatesFromDb(): Observable<List<Rates>> {
return ratesDao.getAllRates()
.toObservable()
.doOnNext {
for (rate in it) {
Timber.i("Repository DB ${it.size}")
}
}
}
}
In getCurrencyFromApi(), getRates() returns me an Observable<Currency>. I would like this particular function to return Observable<List<Rates>> so that I can use it in Observable.concatArrayEager inside getRates() of CurrencyRepository
Currency contains a Map object which can be transformed into a List object. I am not clear on how to do that inside getCurrencyFromApi()
One of possible solutions is
fun getRatesFromApi(): Observable<List<Rates>> {
return apiInterface.getRates()
.flatMapIterable { it.rates.entries }
.map { Rates(it.key ,it.value) }
.doOnNext { ratesDao.insertRate(it) }
.toList()
.toObservable()
}
I advise you insert items in database in one batch, because it will be more efficient.