I'm stuck on a problem with my app.
I'm trying to upload some photos to a cloud and then, I'll show to the user a list of url to copy, in order to use them in a second time.
I'm using RxWorker for this.
When I enqueue my workers list, everything seems fine (one OneTimeWorkRequestBuilder for each image), but then, when I retrieve the result, seems that my observe is triggered too many times.
In order to make this step suspendable, I've two lists (image and worker ones).
When a work is finished (both failure or success) I cancel both image and worker from their own list.
Here's my code. Thanks to everyone!
My Fragment:
class ImagesUploadFragment : Fragment() {
companion object {
private const val TAG = "ImagesUploadFragment"
private const val PHOTOS_LIST = "PHOTOS_LIST"
private const val WORK_NAME = "PHOTO_UPLOAD"
fun newInstance(optionalData: String?): ImagesUploadFragment {
val imagesUploadFragment = ImagesUploadFragment()
val bundle = Bundle()
bundle.putString(PHOTOS_LIST, optionalData)
imagesUploadFragment.arguments = bundle
return imagesUploadFragment
}
}
private lateinit var imagesUploadBinding: FragmentImagesUploadBinding
private var stepListener: StepListener? = null
private val mDownloadUrlsList = mutableListOf<String>()
private val mWorkManger: WorkManager by lazy { WorkManager.getInstance(requireContext()) }
private val mWorkRequestList = mutableListOf<OneTimeWorkRequest>()
private var isUploadPaused = false
private val mImagesList = mutableListOf<String>()
private val gson: Gson by inject()
override fun onAttach(context: Context) {
super.onAttach(context)
stepListener = context as? StepListener
}
private val imagesUploadViewModel: ImagesUploadViewModel by viewModel()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
imagesUploadBinding = FragmentImagesUploadBinding.inflate(inflater, container, false)
return imagesUploadBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val imagesList = arguments?.getString(PHOTOS_LIST)
imagesList?.let {
startImagesUpload(it)
}
imagesUploadBinding.successfullyUpdated.text = String.format(getString(R.string.succeeded_updates), 0)
imagesUploadBinding.workActionIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_pause))
imagesUploadBinding.buttonContinue.setOnClickListener {
stepListener?.onStepChange(4, gson.toJson(mDownloadUrlsList))
}
mWorkManger.cancelAllWorkByTag(TAG)
mWorkManger.cancelAllWork()
mWorkManger.pruneWork()
imagesUploadBinding.workActionIv.setOnClickListener {
if (!isUploadPaused) {
isUploadPaused = true
pauseUpload()
imagesUploadBinding.workActionIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_play))
} else {
isUploadPaused = false
resumeUpload()
imagesUploadBinding.workActionIv.setImageDrawable(ContextCompat.getDrawable(requireContext(), R.drawable.ic_pause))
}
}
setupWorkManagerObserve()
with(imagesUploadViewModel) {
observe(imagesListResource,
success = { imagesList ->
imagesUploadBinding.uploadProgress.max = imagesList.first.size
mImagesList.addAll(imagesList.first.map { it.jsonValue })
setupWorkerList()
},
failure = {
Timber.e(it)
}
)
observe(uploadedFileResource,
success = {
if(it.isNotEmpty()) mDownloadUrlsList.add(it)
imagesUploadBinding.buttonContinue.isVisible = mImagesList.size == 0
imagesUploadBinding.workActionIv.isVisible = mImagesList.size > 0
imagesUploadBinding.horizontalSpace.isVisible = mImagesList.size > 0
},
failure = {
Timber.e(it)
}
)
}
}
private fun pauseUpload() {
mWorkManger.cancelAllWork()
}
private fun resumeUpload() {
setupWorkerList()
}
private fun startImagesUpload(imagesListJson: String) {
imagesUploadViewModel.getImagesFromUri(imagesListJson)
}
private fun setupWorkManagerObserve() {
mWorkManger
.getWorkInfosByTagLiveData(TAG)
.observe(viewLifecycleOwner) {
var successNumber = 0
var failureNumber = 0
it.forEach { work ->
when {
work.state == WorkInfo.State.SUCCEEDED && work.state.isFinished -> {
successNumber++
val image = work.outputData.getString(PHOTO)
val uploadedFileJson = work.outputData.getString(UPLOAD_RESPONSE)
mWorkRequestList.removeAll { it.id == work.id }
mImagesList.removeAll { it == image }
uploadedFileJson?.let {
imagesUploadViewModel.getDownloadUrlFromResponse(it)
}
imagesUploadBinding.uploadProgress.progress = successNumber + failureNumber
imagesUploadBinding.successfullyUpdated.text =
String.format(getString(R.string.succeeded_updates), successNumber)
}
work.state == WorkInfo.State.RUNNING -> {
}
work.state == WorkInfo.State.FAILED -> {
failureNumber++
val image = work.outputData.getString(PHOTO)
mWorkRequestList.removeAll { it.id == work.id }
mImagesList.removeAll { it == image }
imagesUploadBinding.uploadProgress.progress = successNumber + failureNumber
imagesUploadBinding.failuredUpdated.text =
String.format(getString(R.string.failed_updates), failureNumber)
imagesUploadViewModel.addFailure()
}
else -> {
}
}
}
}
}
private fun setupWorkerList() {
for (i in 0 until mImagesList.size) {
val work = OneTimeWorkRequestBuilder<ImagesUploadWorkManager>()
.addTag(TAG)
.setInputData(Data.Builder().putString(PHOTO, mImagesList[i])
.build())
.build()
mWorkRequestList.add(work)
}
mWorkManger.enqueue(mWorkRequestList)
}
}
My worker:
class ImagesUploadWorkManager(val context: Context, parameters: WorkerParameters) : RxWorker(context, parameters) {
companion object {
const val PHOTO = "PHOTO"
const val UPLOAD_RESPONSE = "UPLOAD_RESPONSE"
}
private val uploadFilesUseCase: UploadFilesUseCase by inject(UploadFilesUseCase::class.java)
private val gson: Gson by inject(Gson::class.java)
override fun createWork(): Single<Result> {
val imageStringUri = inputData.getString(PHOTO)
val imageUri = Uri.parse(imageStringUri)
return Single.fromObservable(
uploadFilesUseCase.buildObservable(UploadFilesUseCase.Params(imageUri))
.doOnError {
Timber.e(it)
}
.map {
Result.success(workDataOf(UPLOAD_RESPONSE to gson.toJson(it), PHOTO to imageStringUri))
}
.onErrorReturn {
Timber.e(it)
Result.failure(workDataOf(PHOTO to imageStringUri))
}
)
}
}
My UseCase:
class UploadFilesUseCase(
private val context: Context,
private val getServerUseCase: GetServerUseCase,
) : UseCase<UploadFilesUseCase.Params, GoFileDataEntity>() {
private val goFileApi: GoFileApi by inject(GoFileApi::class.java)
override fun buildObservable(param: Params): Observable<GoFileDataEntity> {
return Observable.just(param.imageUri)
.flatMap {
val file = createTempFile(it) ?: throw Exception()
Observable.just(file)
}.flatMap { imageFile ->
getServerUseCase.buildObservable(GetServerUseCase.Params())
.flatMap { server ->
goFileApi.uploadFile(
String.format(BuildConfig.api_gofile_upload, server.server),
MultipartBody.Part.createFormData("file", imageFile.name, createUploadRequestBody(imageFile, "image/jpeg"))
).toObservable()
}.map { it.goFileDataDTO.toEntity() }
}
}
private fun createUploadRequestBody(file: File, mimeType: String) =
file.asRequestBody(mimeType.toMediaType())
private fun createTempFile(uri: Uri): File? {
val tempFile = File.createTempFile(System.currentTimeMillis().toString().take(4), ".jpg", context.externalCacheDir)
val inputStream = context.contentResolver.openInputStream(uri) ?: return null
FileOutputStream(tempFile, false).use { outputStream ->
var read: Int
val bytes = ByteArray(DEFAULT_BUFFER_SIZE)
while (inputStream.read(bytes).also { read = it } != -1) {
outputStream.write(bytes, 0, read)
}
outputStream.close()
}
inputStream.close()
return tempFile
}
data class Params(val imageUri: Uri)
}
I had a similar problem, and it looks like the queue was full from previous Workmanager enqueuings.
I did following during application start and now it works fine:
WorkManager.getInstance(mContext).cancelAllWork();
Related
I'm doing a practise with the rick and morty api and I have two fragments, one with a recycleview with the characters and another one with the detail of the character, where also you can update some of the values.
My problem is that when I update a value, if I go back to the main fragment with the recycle view, that value is updated but when I go back again to the detail, the value is again the original one. I don't know how to fix it.
This is my detail fragment:
class GetCharacterDetail: Fragment() {
private var binding: CharacterDetailFragmentBinding by autoCleared()
private val viewModel: CharacterDetailViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = CharacterDetailFragmentBinding.inflate(inflater, container, false)
val edit = binding.editButton
val save = binding.saveBotton
changeStateOnEdit(edit, save)
save.setOnClickListener {
val gender = binding.characterGenderText.text.toString()
val status = binding.characterStatusText.text.toString()
val species = binding.characterSpeciesText.text.toString()
updateCharacterDetails(gender, status, species, edit, save)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.getInt("id")?.let { viewModel.start(it) }
setupObservers()
}
private fun setupObservers() {
viewModel.character.observe(viewLifecycleOwner, Observer {
when (it.status) {
Status.StatusEnum.SUCCESS -> {
bindCharacter(it.data!! as CharacterEntity)
binding.progressBar.visibility = View.GONE
binding.characterDetailLayout.visibility = View.VISIBLE
}
Status.StatusEnum.ERROR ->
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
Status.StatusEnum.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.characterDetailLayout.visibility = View.GONE
}
}
})
}
private fun bindCharacter(character: CharacterEntity) {
if (character != null) {
binding.characterName.text = character.name
binding.characterSpeciesText.setText(character.species)
binding.characterStatusText.setText(character.status)
binding.characterGenderText.setText(character.gender)
Glide.with(binding.root)
.load(character.image)
.into(binding.characterImage)
}
}
private fun changeStateOnEdit(edit: ImageButton, save: MaterialButton) {
edit.setOnClickListener(View.OnClickListener {
edit.isVisible = false
binding.characterGender.isEnabled = true
binding.characterSpecies.isEnabled = true
binding.characterStatus.isEnabled = true
save.isVisible = true
})
}
private fun updateCharacterDetails(gender: String, status: String, species: String,edit: ImageButton, save: MaterialButton) {
viewModel.updateCharacterDetails(gender, status, species)
viewModel.character.observe(viewLifecycleOwner, Observer {
when (it.status) {
Status.StatusEnum.SUCCESS -> {
Toast.makeText(activity, "Personaje actualizado correctamente", Toast.LENGTH_SHORT).show()
edit.isVisible = true
binding.characterGender.isEnabled = false
binding.characterSpecies.isEnabled = false
binding.characterStatus.isEnabled = false
save.isVisible = false
bindCharacter(it.data!!)
}
Status.StatusEnum.ERROR ->
Toast.makeText(activity, it.message, Toast.LENGTH_SHORT).show()
Status.StatusEnum.LOADING -> {
binding.progressBar.visibility = View.VISIBLE
binding.characterDetailLayout.visibility = View.GONE
}
}
})
}
}
And this is my ViewModel:
class CharacterDetailViewModel #Inject constructor(
private val repository: CharacterRepository
) : ViewModel() {
private val idCharacter = MutableLiveData<Int>()
val character = idCharacter.switchMap { id ->
repository.getCharacter(id)
}
fun updateCharacterDetails(gender: String, status: String, species: String) {
viewModelScope.launch {
withContext(Dispatchers.IO) {
val id = idCharacter.value ?: return#withContext
repository.updateCharacterDetail(id, gender, status, species)
}
}
}
fun start(id: Int) {
idCharacter.value = id
}
}
Herew is the repository:
class CharacterRepository #Inject constructor(
private val api : CharacterService,
private val characterDao: CharacterDao
) {
fun getAllCharacters() = getEntitiesOperation(
databaseQuery = { characterDao.getAllCharacters() },
networkCall = { api.getCharacters() },
saveCallResult = { characterDao.insertAll(it.results) }
)
fun getCharacter(id: Int) = getEntitiesOperation(
databaseQuery = { characterDao.getCharacter(id) },
networkCall = { api.getCharacter(id) },
saveCallResult = { characterDao.insert(it) }
)
fun deleteCharacter(id: Int) = characterDao.deleteCharacter(id)
fun updateCharacterDetail(id: Int, gender:String, status:String, species:String) =
characterDao.updateCharacterDetail(id, gender, status, species)
}
And the function I use to take the data from local database if there is data in it. Here is where I think it is the problem since I think that something has to be recovered wrong and that localData is null and then the method look for the data on the api
fun <T, A> getEntitiesOperation(databaseQuery: () -> LiveData<T>,
networkCall: suspend () -> Status<A>,
saveCallResult: suspend (A) -> Unit):
LiveData<Status<T>> = liveData(Dispatchers.IO) {
emit(Status.loading())
val source = databaseQuery.invoke().map { Status.success(it) }
emitSource(source)
val localData = source.value?.data
if (localData != null) return#liveData
val responseStatus = networkCall.invoke()
if (responseStatus.status == StatusEnum.SUCCESS) {
saveCallResult(responseStatus.data!!)
} else if (responseStatus.status == StatusEnum.ERROR) {
emit(Status.error(responseStatus.message!!))
emitSource(source)
}
}
I've been with this problem all day and I don't know what to do or how to fix it. Thank you in advance for the help
i'm trying to mute everyone in the conference call to prevent trolling/spamming, but the problem is allowed to umute audio and video at anytime even tho i'm setting the options to be muted by default. P.S : if you have any idea that may help me to prevent this issue from going on into the conference call please just write it down.
MeetingUtils.kt
object MeetingUtils {
private var isMuted: Boolean = true
fun startMeeting(context: Context, meetingCode: String) {
val serverUrl = URL(context.getString(R.string.app_server_url))
val defaultOptions = JitsiMeetConferenceOptions.Builder()
.setServerURL(serverUrl)
.setWelcomePageEnabled(false)
.setAudioMuted(isMuted)
.setVideoMuted(true)
.setFeatureFlag("invite.enabled", false)
.setFeatureFlag("live-streaming.enabled", false)
.setFeatureFlag("meeting-name.enabled", false)
.setFeatureFlag("call-integration.enabled", false)
.setFeatureFlag("recording.enabled", false)
.build()
JitsiMeet.setDefaultConferenceOptions(defaultOptions)
val options = JitsiMeetConferenceOptions.Builder()
.setRoom(meetingCode)
.setUserInfo(null)
val sharedPrefData= SharedPrefData(context)
val currentUser = FirebaseAuth.getInstance().currentUser
if (sharedPrefData.getSkip().equals("Skip"))
{
val userInfoBundle = bundleOf(
"displayName" to "User Not Sign in",
"email" to "Please Sign In",
"avatarURL" to R.drawable.ic_account
)
options.setUserInfo(JitsiMeetUserInfo(userInfoBundle))
}
else
{
if (currentUser != null) {
val userInfoBundle = bundleOf(
"displayName" to sharedPrefData.getName(),
"email" to sharedPrefData.getEmail(),
"avatarURL" to sharedPrefData.getImage()
)
options.setUserInfo(JitsiMeetUserInfo(userInfoBundle))
}
val userInfoBundle = bundleOf(
"displayName" to sharedPrefData.getName() ,
"email" to sharedPrefData.getEmail(),
"avatarURL" to "http://graph.facebook.com/${sharedPrefData.getAuthId()}/picture?type=square"
)
options.setUserInfo(JitsiMeetUserInfo(userInfoBundle))
}
JitsiMeetActivity.launch(context, options.build())
}
}
HomeFragment.kt
class HomeFragment : Fragment() {
private var binding: FragmentHomeBinding? = null
private val minMeetingCodeLength = 10
private var currentUser: FirebaseUser? = null
var email:String?=null
var firstName:String?=null
var lastName:String?=null
var profileImage:String?=null
private val viewModel by viewModel<MainViewModel>()
lateinit var auth: FirebaseAuth
private var sharedPrefData: SharedPrefData?=null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding=FragmentHomeBinding.inflate(inflater,container,false)
val view = binding!!.root
onCreateMeetingCodeChange()
onCopyMeetingCodeFromClipboardClick()
onShareMeetingCodeClick()
onJoinMeetingClick()
onCreateMeetingClick()
onMeetingToggleChange()
val mAdView: AdView = view.findViewById(R.id.adView)
val adRequest = AdRequest.Builder().build()
mAdView.loadAd(adRequest)
return view
}
companion object{
fun newInstance(text: String?): HomeFragment? {
val f = HomeFragment()
val b = Bundle()
b.putString("msg", text)
f.arguments = b
return f
}
}
private fun onMeetingToggleChange() {
binding?.tgMeeting?.addOnButtonCheckedListener { toggleGroup, checkedId, isChecked ->
if (isChecked) {
when (checkedId) {
R.id.btnToggleJoinMeeting -> {
binding?.groupCreateMeeting?.makeGone()
binding?.groupJoinMeeting?.makeVisible()
}
R.id.btnToggleCreateMeeting -> {
binding?.groupJoinMeeting?.makeGone()
binding?.groupCreateMeeting?.makeVisible()
val meetingCode = generateMeetingCode()
binding?.etCodeCreateMeeting?.setText(meetingCode)
}
}
}
}
}
private fun onCreateMeetingCodeChange() {
binding?.tilCodeCreateMeeting?.etCodeCreateMeeting?.doOnTextChanged { text, start, before, count ->
if (count >= minMeetingCodeLength) binding?.tilCodeCreateMeeting!!.error = null
}
}
private fun generateMeetingCode(): String {
val allowedChars = ('A'..'Z') + ('a'..'z')
return (1..10)
.map { allowedChars.random() }
.joinToString("")
}
private fun onCopyMeetingCodeFromClipboardClick() {
binding?.tilCodeJoinMeeting?.setEndIconOnClickListener {
val clipboardText = activity?.getTextFromClipboard()
if (clipboardText != null) {
binding?.etCodeJoinMeeting?.setText(clipboardText)
activity?.toast(getString(R.string.main_meeting_code_copied))
} else {
activity?.toast(getString(R.string.main_empty_clipboard))
}
}
}
private fun onShareMeetingCodeClick() {
binding?.tilCodeCreateMeeting?.setEndIconOnClickListener {
if (binding?.etCodeCreateMeeting?.text.toString().length >= minMeetingCodeLength) {
binding!!.tilCodeCreateMeeting.error = null
activity?.startShareTextIntent(
getString(R.string.main_share_meeting_code_title),
"Meeting Code: "+binding!!.etCodeCreateMeeting.text.toString()+"\n "+
getString(R.string.profile_share_app_text, activity!!. applicationContext.packageName)
)
} else {
binding!!.tilCodeCreateMeeting.error =
getString(R.string.main_error_meeting_code_length, minMeetingCodeLength)
}
}
}
private fun onJoinMeetingClick() {
binding?.btnJoinMeeting?.setOnClickListener {
if (binding!!.etCodeJoinMeeting.text.toString().length >= minMeetingCodeLength) {
joinMeeting()
} else {
activity?.toast(getString(R.string.main_error_meeting_code_length, minMeetingCodeLength))
}
}
}
private fun joinMeeting() {
activity?.let {
MeetingUtils.startMeeting(
it,
binding?.etCodeJoinMeeting?.text.toString())
} // Start Meeting
viewModel.addMeetingToDb(
Meeting(
binding?.etCodeJoinMeeting?.text.toString(),
System.currentTimeMillis()
)
) // Add meeting to db
}
private fun onCreateMeetingClick() {
binding?.btnCreateMeeting?.setOnClickListener {
if (binding!!.etCodeCreateMeeting.text.toString().length >= minMeetingCodeLength) {
createMeeting()
} else {
activity?.toast(getString(R.string.main_error_meeting_code_length, minMeetingCodeLength))
}
}
}
private fun createMeeting() {
activity?.let {
MeetingUtils.startMeeting(
it,
binding?.etCodeCreateMeeting?.text.toString()
)
} // Start Meeting
viewModel.addMeetingToDb(
Meeting(
binding?.etCodeCreateMeeting?.text.toString(),
System.currentTimeMillis()
)
) // Add meeting to db
}
}
I am using exoplayer for playing videos .And for this we are used Fragment instance with pagerstateadapter and viewpager2.
But when scroll fast previous played video sound listen in background as well as in screen video means mix the sound in same video player.
Please help me how to solve this.
1.State adapter
class StoriesPagerAdapter(
fragment: Fragment,
val onClick1: VideoItemAdapter.OnItemClicked?,
val onlikeClick: VideoItemAdapter.OnLikeCLicked?,
val onFollowClick: VideoItemAdapter.OnFollowCLicked?,
val ontrendingClick: VideoItemAdapter.OnTrendCLicked?,
val oniconCLick: VideoItemAdapter.OnIconClick?) : FragmentStateAdapter(fragment) {
val dataList:MutableList<Gettrendingvideos.Data.Postlist>=mutableListOf()
override fun getItemCount(): Int {
return dataList.size
}
fun addAll(movies: MutableList<Gettrendingvideos.Data.Postlist>) {
for (movie in movies) {
add(movie)
}
}
fun add(moive: Gettrendingvideos.Data.Postlist) {
dataList.add(moive)
notifyItemInserted(dataList.size - 1)
}
override fun createFragment(position: Int): Fragment {
return StoryViewFragment.newInstance(
onClick1,
onlikeClick,
onFollowClick,
ontrendingClick,
oniconCLick,
dataList[position]
)
}}
2 Fragment
class StoryViewFragment : Fragment(), CommentFragment.onCommentCountIncrease {
private var storyUrl: String? = null
private var storiesDataModel: Gettrendingvideos.Data.Postlist? = null
lateinit var mView: View
private var simplePlayer: SimpleExoPlayer? = null
private var cacheDataSourceFactory: CacheDataSourceFactory? = null
private val simpleCache = MainApplication.simpleCache
private var toPlayVideoPosition: Int = -1
lateinit var viewModel: MainViewModel
lateinit var preferences: SecurePreferences
private var bool: Boolean? = false
var onItemClick: VideoItemAdapter.OnItemClicked? = null
var onlikeCLicked: VideoItemAdapter.OnLikeCLicked? = null
var onFollowCLicked: VideoItemAdapter.OnFollowCLicked? = null
var onTrendCLicked: VideoItemAdapter.OnTrendCLicked? = null
var onIconClick: VideoItemAdapter.OnIconClick? = null
lateinit var huserId: String
lateinit var token: String
companion object {
fun newInstance(
itemClicked: VideoItemAdapter.OnItemClicked?,
likeCLicked: VideoItemAdapter.OnLikeCLicked?,
onFollowCLicked: VideoItemAdapter.OnFollowCLicked?,
onTrendCLicked: VideoItemAdapter.OnTrendCLicked?,
onIconClick: VideoItemAdapter.OnIconClick?,
storiesDataModel: Gettrendingvideos.Data.Postlist
) = StoryViewFragment()
.apply {
arguments = Bundle().apply {
putParcelable(Constants.KEY_STORY_DATA, storiesDataModel)
}
this.onItemClick = itemClicked
this.onlikeCLicked = likeCLicked
this.onFollowCLicked = onFollowCLicked
this.onTrendCLicked = onTrendCLicked
this.onIconClick = onIconClick
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
mView = inflater.inflate(
R.layout.layout_main,
container,
false
)
return mView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, ViewModelFactory(RetrofitBuilder.apiService))
.get(MainViewModel::class.java)
preferences =
SecurePreferences(
requireActivity(),
AppConstants.preferenceName,
AppConstants.USER_DETAILS,
true
)
storiesDataModel = arguments?.getParcelable(Constants.KEY_STORY_DATA)
setData()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (!preferences.getString(AppConstants.USER_ID).equals(null)) {
huserId = preferences.getString(AppConstants.USER_ID)!!
Log.d("TAG", "onActivityCreated: $huserId")
}
}
#SuppressLint("SetTextI18n")
private fun setData() {
Log.d("TAG", "setData: $storiesDataModel")
mView.textview2.text = storiesDataModel?.user_name
mView.like_count.text = storiesDataModel?.total_likes.toString()
comment_count.text = storiesDataModel?.total_comments.toString()
mView.textview.text = storiesDataModel?.type
Glide.with(this).load(storiesDataModel?.user_profile_pic).placeholder(R.drawable.profile).into(mView.image)
if (storiesDataModel?.is_like == 0) {
mView.imageView4.setImageResource(R.drawable.ic_like)
} else {
mView.imageView4.setImageResource(R.drawable.ic_like_red)
}
if (storiesDataModel?.is_following!! == 0) {
mView.textview3.text = "Follow"
} else {
mView.textview3.text = "Following"
}
if (storiesDataModel?.user_id.toString()==preferences.getString(AppConstants.USER_ID)) {
mView.textview3.visibility = View.GONE
}
image.setOnClickListener {
if (preferences.getString(AppConstants.token).equals(null)) {
MainActivity().show(childFragmentManager, "")
} else {
preferences.put(
AppConstants.OtherProfile_UserId,
storiesDataModel?.user_id.toString()
)
}
}
val simplePlayer = getPlayer()
player_view_story.player = simplePlayer player_view_story.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL)
simplePlayer?.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
storyUrl = storiesDataModel?.video_url
prepareMedia(storiesDataModel)
}
override fun onPause() {
pauseVideo()
super.onPause()
}
override fun onResume() {
restartVideo()
super.onResume()
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private fun pausePlayer() {
simplePlayer?.setPlayWhenReady(false)
simplePlayer?.getPlaybackState()
}
private fun startPlayer() {
simplePlayer?.setPlayWhenReady(true)
simplePlayer?.getPlaybackState()
}
private val playerCallback: Player.EventListener? = object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
}
override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException?) {
super.onPlayerError(error)
}
}
private fun prepareVideoPlayer() {
simplePlayer = ExoPlayerFactory.newSimpleInstance(context)
cacheDataSourceFactory = CacheDataSourceFactory(
simpleCache,
DefaultHttpDataSourceFactory(
Util.getUserAgent(
context,
"exo"
)
)
)
}
private fun getPlayer(): SimpleExoPlayer? {
if (simplePlayer == null) {
prepareVideoPlayer()
}
return simplePlayer
}
private fun prepareMedia(datamodel: Gettrendingvideos.Data.Postlist?{
val uri = Uri.parse(datamodel?.video_url)
simplePlayer.repeatMode = Player.REPEAT_MODE_ONE
simplePlayer.playWhenReady = true
if (storiesDataModel!!.type == "following") {
following_page.typeface = Typeface.DEFAULT_BOLD
trending_page.setTypeface(null, Typeface.NORMAL)
} else {
following_page.setTypeface(null, Typeface.BOLD)
}
if (storiesDataModel.type == "treading") {
trending_page.typeface = Typeface.DEFAULT_BOLD
following_page.setTypeface(null, Typeface.NORMAL)
} else {
trending_page.setTypeface(null, Typeface.BOLD)
}
if (simplePlayer.playWhenReady == true) {
}
simplePlayer.addListener(playerCallback)
toPlayVideoPosition = -1
}
private fun setArtwork(drawable: Drawable, playerView: PlayerView) {
playerView.useArtwork = true
playerView.defaultArtwork = drawable
}
private fun playVideo() {
simplePlayer.playWhenReady = true
}
private fun restartVideo() {
if (simplePlayer == null) {
prepareMedia(storiesDataModel)
} else {
simplePlayer.seekToDefaultPosition()
simplePlayer.playWhenReady = true
}
}
private fun pauseVideo() {
simplePlayer.playWhenReady = false
}
private fun releasePlayer() {
simplePlayer.stop(true)
simplePlayer.release()
}}
override fun setMenuVisibility(menuVisible: Boolean) {
if (!menuVisible){
simplePlayer?.playWhenReady = false
simplePlayer?.pause()
}
super.setMenuVisibility(menuVisible)
}
JUST ADD THIS IN YOUR StoryViewFragment.
I have two screens first one has recycler view list of data and searchView above it that's filter data in this recycler, the view Model code of the first fragment
class MscInspectionViewModel(val activity: LaunchActivity, val mRootView: MscInspectFragment) :
BaseViewModel(),
SwipeRefreshLayout.OnRefreshListener {
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
private val getDataError = MutableLiveData<Boolean>()
var listType = MutableLiveData<Int>()
val hint = MutableLiveData<String>()
private var isRefreshing: Boolean = false
private var mSharedPreferences: SharedPreferences? = null
val dataListAdapter = ContainersUnderCheckAdapter(activity)
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val filterDataByTab = object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab!!.text) {
activity.resources.getString(R.string.cidPending) -> {
listType.value = 0
getPendingData()
}
activity.resources.getString(R.string.cidDone) -> {
listType.value = 1
getDoneData()
}
}
}
}
val filterData = object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (query.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(query)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(newText)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
}
return false;
}
}
val closeImgListener = View.OnClickListener {
mRootView.svSearchMSC.setQuery("", true)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
init {
listType.value = 0
mSharedPreferences = getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = activity.resources.getString(R.string.mscInspectTitle)
hint.value = activity.resources.getString(R.string.msc_search)
getData()
}
fun getData() {
onRetrievePostListStart()
subscription = apiAccount.getContainersUnderCheck(
"getContainersUnderCheck",
mSharedPreferences!!.getString(Constants.CFID, "")!!,
mSharedPreferences!!.getString(Constants.CFTOKEN, "")!!
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {}
.doOnTerminate {}
.subscribe({ result ->
result?.let {
if (result.ResponseCode != null && result.ResponseCode.trim() != "000") {
onRetrievePostListError(result.ResponseMessage)
} else {
result.ContainersData?.let { it1 -> onRetrievePostListSuccess(it1) }
}
}
}, { throwable ->
android.util.Log.e("getDataInquiry", throwable.message!!)
onRetrievePostListError(activity.resources.getString(R.string.general_error))
})
}
private fun getPendingData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun getDoneData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
isRefreshing = false
}
private fun onRetrievePostListSuccess(containersData: List<ContainersData>) {
onRetrievePostListFinish()
dataListAdapter.updateInquiryAdapter(containersData as ArrayList<ContainersData>)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
override fun onRefresh() {
isRefreshing = true
getData()
}
}
adapter is :
class ContainersUnderCheckAdapter(val activity: LaunchActivity) :
RecyclerView.Adapter<ContainersUnderCheckAdapter.ViewHolder>() {
private lateinit var mDataSet: ArrayList<ContainersData>
private lateinit var mDataSetFiltered: ArrayList<ContainersData>
fun updateInquiryAdapter(dataSet: ArrayList<ContainersData>) {
mDataSet = ArrayList()
mDataSet.clear()
mDataSet.addAll(dataSet)
mDataSetFiltered = mDataSet
getFilter(2, 1).filter("PENDING")
// notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerItemFieldLayoutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_item_field_layout,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return if (::mDataSetFiltered.isInitialized) mDataSetFiltered.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mDataSetFiltered[position])
}
operator fun get(position: Int): ContainersData {
return mDataSetFiltered.get(position)
}
/**
* #filterType :
* IF 1 : filter on Data Type RJCTD + APPROVED
* 2 : filter on Data Type PENDING
* 3 :
*/
fun getFilter(filterType: Int, listType: Int): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val charString = charSequence.toString()
mDataSetFiltered = if (charString.isEmpty()) {
mDataSet
} else {
val filteredList = ArrayList<ContainersData>()
for (row in mDataSet) {
when (filterType) {
1 -> {
if (row.status == "RJCTD" || row.status == "APPROVED") {
filteredList.add(row)
}
}
2 -> {
if (row.status == charString) {
filteredList.add(row)
}
}
3 -> {
when (listType) {
0 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status == "PENDING"
) {
filteredList.add(row)
}
}
1 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status != "PENDING"
) {
filteredList.add(row)
}
}
}
}
}
}
filteredList
}
val filterResults = FilterResults()
filterResults.values = mDataSetFiltered
return filterResults
}
override fun publishResults(
charSequence: CharSequence,
filterResults: FilterResults
) {
if (::mDataSetFiltered.isInitialized) {
mDataSetFiltered = try {
filterResults.values as ArrayList<ContainersData>
} catch (e: Exception) {
Log.e("mDataSetFiltered",e.message!!)
ArrayList()
}
when (filterType) {
1->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p1!!.UpdateDate.compareTo(p0!!.UpdateDate) })
}
2->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p0!!.ID!!.compareTo(p1.ID!!) })
}
}
}
// refresh the list with filtered data
notifyDataSetChanged()
}
}
}
class ViewHolder(
private val binding: ContianerItemFieldLayoutBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscInspectionListViewModel(activity)
fun bind(data: ContainersData) {
viewModel.bind(data)
binding.viewModel = viewModel
}
}
}
any data in this recycler on click go to fragment has tow recycler first one to show data, the second one to pick Images
the second-page code
class MSCDataFragment : Fragment() {
lateinit var rootView: View
lateinit var activity: LaunchActivity
lateinit var utility: Utility
lateinit var loadingView: LoadingView
private lateinit var viewModel: MSCDataViewModel
private lateinit var binding: FragmentMscdataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getActivity() != null) {
activity = getActivity() as LaunchActivity
utility = Utility(activity)
loadingView = LoadingView(activity)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mscdata, container, false)
rootView = binding.root
initial()
return rootView
}
private fun initial() {
viewModel = ViewModelProvider(
this, ViewModelFactory(
activity,
arguments!!.getSerializable("Data") as ContainersData
)
).get(MSCDataViewModel::class.java)
binding.viewModel = viewModel
// binding.imgList.layoutManager = GridLayoutManager(activity, 3)
binding.containerInfo.layoutManager = LinearLayoutManager(activity)
binding.openCIDNotValid.typeface =
Typeface.createFromAsset(activity.assets, "Bahij_Janna-Regular.ttf")
binding.openCIDNotValid.setOnCheckedChangeListener(viewModel.onOpenCidNotValidListener)
viewModel.loading.observe(this, Observer { loading ->
loading?.let {
if (it) {
loadingView.show()
} else {
loadingView.dismiss()
}
}
})
viewModel.errorMessage.observe(this, Observer { msg ->
msg?.let {
utility.ShowToast(msg)
}
})
viewModel.imagesAdapters2.observe(this, Observer { msg ->
msg?.let {
binding.imgList.apply {
layoutManager = GridLayoutManager(activity, 3)
adapter = it
}
}
})
rootView.toolbar_Back.setOnClickListener(viewModel.backClickListener)
binding.btnAddImages.setOnClickListener(viewModel.pickImages)
binding.successContianer.setOnClickListener(viewModel.correctContainer)
binding.damagedContianer.setOnClickListener(viewModel.wrongContainer)
}
}
the view model is :
class MSCDataViewModel(val activity: LaunchActivity, val containersData: ContainersData) :
BaseViewModel(), GetImagesListener {
#Inject
lateinit var restApiAccount: RestApiAccount
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
val ButtonText: MutableLiveData<String> = MutableLiveData()
var openCIDNotValidVisibility = MutableLiveData<Int>()
private val getDataError = MutableLiveData<Boolean>()
val btnImagesVisibility = MutableLiveData<Int>()
var imgeNoteVisibility = MutableLiveData<Int>()
var successVisibility = MutableLiveData<Int>()
var damagedVisibility = MutableLiveData<Int>()
var notesVisibility = MutableLiveData<Int>()
val btnVisibility = MutableLiveData<Int>()
var canNotOpen = MutableLiveData<Int>()
private val images = ArrayList<Image>()
var utility = Utility(activity)
private var CURRENTINDEX = 0
private var mSharedPreferences: SharedPreferences? = null
val DataListAdapter = ContainerDataAdapter(activity)
var imagesAdapter = ContainerImagesAdapter(activity, containersData.status!!, ArrayList())
val imagesAdapters2 = MutableLiveData<ContainerImagesAdapter2>()
val userInfo: UserInfo
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val pickImages = View.OnClickListener {
pickImages()
}
val correctContainer = View.OnClickListener {}
val onOpenCidNotValidListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
successVisibility.value = View.GONE
canNotOpen.value = 1
} else {
canNotOpen.value = 0
successVisibility.value = View.VISIBLE
}
}
val wrongContainer = View.OnClickListener {}
var mscNotes: ObservableField<String> = ObservableField("")
init {
canNotOpen.value = 0
mSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = containersData.CID
ButtonText.value = activity.resources.getString(R.string.cleanContianer)
userInfo = utility.readObjectFromSharedPreferences(
mSharedPreferences,
Constants.USER_INFO_KEY,
UserInfo::class.java
) as UserInfo
openCIDNotValidVisibility.value = View.GONE
fillData()
}
private fun fillData() {
val data: LinkedHashMap<String, String> = containersData.data!!
val captionsMap = utility.readObjectFromSharedPreferences(
mSharedPreferences, Constants.CAPTIONS_MAP_KEY,
HashMap::class.java
) as HashMap<String, String>
if (containersData.data.size > 0) {
val list = ArrayList<KeyValueModel>()
for (inside in data.keys) {
val ky = captionsMap[inside]
val value = data[inside].toString()
ky?.let { KeyValueModel(it, value) }?.let { list.add(it) }
}
DataListAdapter.updateInquiryAdapter(list)
} else {
errorMessage.value = activity.resources.getString(R.string.no_data)
}
if (containersData.ImageList != null && containersData.ImageList.isNotEmpty()) {
imagesAdapter.updateContainerImagesAdapter(containersData.ImageList)
}
}
private fun pickImages() {
activity.setCallBack(this)
val pictureDialog: AlertDialog
val builder = activity.let { AlertDialog.Builder(it) }
val dialogView = View.inflate(activity, R.layout.choose_camera_method, null)
builder.setView(dialogView)
val nafithPopupContainer = dialogView.findViewById<RelativeLayout>(R.id.RLTitle)
nafithPopupContainer.setBackgroundColor(
ContextCompat.getColor(
activity,
R.color.mainColor
)
)
val popUpGallery = dialogView.findViewById<LinearLayout>(R.id.PopupGellary)
val popUpCamera = dialogView.findViewById<LinearLayout>(R.id.PopupCamera)
pictureDialog = builder.create()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Objects.requireNonNull<Window>(pictureDialog.window)
.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
if (pictureDialog.window != null) {
pictureDialog.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
popUpGallery.setOnClickListener {
fromGallery()
pictureDialog.dismiss()
}
popUpCamera.setOnClickListener {
fromCamera()
pictureDialog.dismiss()
}
val popupClose = dialogView.findViewById<ImageView>(R.id.popupClose)
popupClose.setOnClickListener { pictureDialog.dismiss() }
pictureDialog.show()
}
private fun fromGallery() {
ImagePicker.create(activity)
.toolbarImageTitle(activity.resources.getString(R.string.get_image))
.toolbarArrowColor(ContextCompat.getColor(activity, R.color.colorWhite))
.showCamera(false)
.limit(6)
.start()
}
private fun fromCamera() {
ImagePicker.cameraOnly().start(activity)
}
override fun onGetImage(image: Image) {
imgeNoteVisibility.value = View.GONE
imagesAdapter.updateContainerImagesAdapter(image)
images.add(image)
}
override fun addingImagesDone(mImages: MutableList<Image>) {
images.clear()
images.addAll(mImages)
imgeNoteVisibility.value = View.GONE
val listString :ArrayList<String> = ArrayList()
for (i in mImages.indices){
listString.add(mImages[i].path)
}
imagesAdapters2.value = ContainerImagesAdapter2(activity,containersData.status!!,listString)
imagesAdapters2.value!!.notifyItemRangeChanged(0,listString.size)
}
override fun onImgDelete(image: String) {
var x = 0
try {
for (i in 0 until images.size) {
x = i
if (images[i].path == image) {
images.remove(images[i])
}
}
} catch (e: Exception) {
Log.e("errorImages", e.message!!)
Log.e("xx", x.toString())
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
}
private fun onRetrievePostListSuccess(msg: String?) {
onRetrievePostListFinish()
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
}
Adapter code is :
class ContainerImagesAdapter2() : RecyclerView.Adapter<ContainerImagesAdapter2.ViewHolder>() {
var status: String = ""
lateinit var activity: LaunchActivity
lateinit var utility: Utility
constructor(
mActivity: LaunchActivity,
mStatus: String,
pathsList: ArrayList<String>
) : this() {
activity = mActivity
pathsDataSet = pathsList
status = mStatus
utility = Utility(activity)
}
private var pathsDataSet: ArrayList<String> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerImageFieldBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_image_field,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return pathsDataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindPath(pathsDataSet[position], position)
}
inner class ViewHolder(
private val binding: ContianerImageFieldBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscImagesListViewModel(activity)
fun bindPath(data: String, position: Int) {
viewModel.bindPath(data)
binding.viewModel = viewModel
if (status != "PENDING") {
binding.closeImg.visibility = View.GONE
}
binding.closeImg.setOnClickListener {}
binding.mainImg.setOnClickListener {
val fragment = FullImageFragment()
val bundle = Bundle()
val list = ArrayList<String>()
for (item in 0 until pathsDataSet.size) {
list.add(pathsDataSet[item])
}
bundle.putSerializable("ImageList", list)
bundle.putInt("Position", position)
fragment.arguments = bundle
activity.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).addToBackStack(fragment.tag)
.commit()
}
}
}
}
if you filter data using search view in the first-page and pick images in the second page , list of picked images doesn't appear, if you going to the second page without filtering data everything ok
solve Problem found
Just Update constraint-layout library in gradle dependencies to version '2.0.0-beta4'
I'm tying to parse JSON in recyclerview. App compiles fine but it's outputting empty/blank screen
BlogAdapter.kt
class BlogAdapter(private val blogList: List<Blog>) : RecyclerView.Adapter<BlogAdapter.ViewHolder>() {
override fun getItemCount()= blogList.size
private var mContext: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
this.mContext=parent.context;
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.character_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val mBlog = this.blogList[position]
if (mBlog.img != null) {
Glide.with(mContext!!)
.load(mBlog.img)
.into(holder.ivThumbnail)
}
if (mBlog.name != null) {
holder.tvTitle.text = mBlog.name
println("Log: Kabe "+mBlog.name)
}
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val ivThumbnail:ImageView = itemView.findViewById(R.id.ivThumbnail);
val tvTitle:TextView = itemView.findViewById(R.id.tvTitle);
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
var mainViewModel: MainViewModel? = null
var mBlogAdapter: BlogAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
getPopularBlog()
swipe_refresh.setOnRefreshListener { getPopularBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel!!.allBlog.observe(this, Observer { charactersList ->
prepareRecyclerView(charactersList)
})
}
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
My Json file looks like this:
[
{
"id": 1,
"name": "potter",
"img": "https://images.example.com/potter.jpg"
},
{ …}
]
I've created it based on this tutorial: https://itnext.io/kotlin-wrapping-your-head-around-livedata-mutablelivedata-coroutine-networking-and-viewmodel-b552c3a74eec
Any suggestions please
EDIT:
class BlogRepository() {
private var character = mutableListOf<ABCCharacters>()
private var mutableLiveData = MutableLiveData<List<ABCCharacters>>()
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
private val thisApiCorService by lazy {
RestApiService.createCorService()
}
fun getMutableLiveData():MutableLiveData<List<ABCCharacters>> {
coroutineScope.launch {
val request = thisApiCorService.getPopularBlog()
withContext(Dispatchers.Main) {
try {
val response = request.await()
val mBlogWrapper = response;
if (/*mBlogWrapper != null &&*/ mBlogWrapper.isNotEmpty()) {
character = mBlogWrapper as MutableList<ABCCharacters>
mutableLiveData.value = character
}
} catch (e: HttpException) {
// Log exception //
} catch (e: Throwable) {
// Log error //)
}
}
}
return mutableLiveData;
}
}
You forget to call notifyDataSetChanged, when you setup your RecyclerView widget. Below the full method call, to make it works.
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
mBlogAdapter.notifyDataSetChanged()
}
Try using below implementation:
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
var mBlogAdapter: BlogAdapter? = null
var blogList: List<Blog> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// init your RV here
prepareRecyclerView()
getPopularBlog()
swipe_refresh.setOnRefreshListener { mainViewModel.getAllBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel.getAllBlog().observe(this, Observer { charactersList ->
blogList = charactersList
mBlogAdapter?.notifyDataSetChanged()
})
}
private fun prepareRecyclerView() {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
Modify your view model like below:
class MainViewModel() : ViewModel() {
val characterRepository= BlogRepository()
fun getAllBlog(): MutableLiveData<List<ABCCharacters>> {
return characterRepository.getMutableLiveData()
}
override fun onCleared() {
super.onCleared()
characterRepository.completableJob.cancel()
}
}