class Rove3LiveVideoFragment : BaseFragment(){
#Inject
lateinit var roveR3LiveVideoFragmentViewModel: RoveR3LiveVideoFragmentViewModel
#Inject
lateinit var videoControlWidget: VideoControlWidget
#Inject
lateinit var notConnectedWidget: NotConnectedWidget
#Inject
lateinit var appPreference: AppPreference
#Inject
lateinit var videoFullViewWidget: VideoFullViewWidget
lateinit var customLoader: CustomLoader
private lateinit var onFullScreenListener: OnFullScreenListener
var isChangeScreenButtonClicked = false
var isPortraitMode = true
override fun onAttach(context: Context) {
inject(this)
super.onAttach(context)
if (activity is Rove3MainActivity) {
onFullScreenListener = activity as OnFullScreenListener
} else {
throw ClassCastException(
activity.toString()
+ " must implement MyListFragment.OnItemSelectedListener"
)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_rove_r3_live_video, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initWidget(view_live_vide_page)
customLoader = CustomLoader(requireContext())
val wifiManager =
requireContext().applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager?
wifiManager?.let {
if (wifiManager.isWifiEnabled) {
roveR3LiveVideoFragmentViewModel.apply {
customLoader.show(getString(R.string.title_please_wait))
if (appPreference.isAppGoesBackGroundExceptHomePage) {
appPreference.isAppGoesBackGroundExceptHomePage = false
getRove3CameraConnectionStatus(true)
} else {
getRove3CameraConnectionStatus(false)
}
observe(stateLiveData, ::getR3ConnectedInLiveVideFragment)
}
} else {
notConnectedView()
}
}
}
private fun initWidget(view: View) {
videoControlWidget.apply {
initView(view)
addWidget(this)
activity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
observe(onClicked, ::onClickedButton)
}
notConnectedWidget.apply {
initView(view)
addWidget(this)
}
videoFullViewWidget.apply {
initView(view)
activity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
addWidget(this)
observe(onClicked, ::onFullVideoViewControllClick)
}
}
private fun onFullVideoViewControllClick(callToAction: VideoFullViewWidget.CallToAction) {
when (callToAction) {
is VideoFullViewWidget.CallToAction.PortraitMode -> {
activity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
showVideoView(false)
isChangeScreenButtonClicked = true
}
}
}
private fun onClickedButton(callToAction: VideoControlWidget.CallToAction) {
when (callToAction) {
is VideoControlWidget.CallToAction.LandScapeMode -> {
activity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
isChangeScreenButtonClicked = true
showVideoView(true)
}
}
}
override fun onPause() {
super.onPause()
Log.d("CALLBACKK", "onPause")
}
private fun getR3ConnectedInLiveVideFragment(state: BaseViewModel.LiveVideFragmentState) {
}
private fun notConnectedView() {
customLoader.hide()
notConnectedWidget.show()
view_surface.visibility = View.GONE
videoControlWidget.hide()
}
private fun showLiveVideoWhenR3Connected() {
notConnectedWidget.hide()
view_surface.visibility = View.VISIBLE
videoControlWidget.show()
}
private fun addWidget(widget: Widget) {
lifecycle.addObserver(widget)
}
private fun showVideoView(isFullVideoView: Boolean) {
onFullScreenListener.onFullSreen(isFullVideoView)
if (isFullVideoView) {
videoControlWidget.hide()
videoFullViewWidget.show()
val params = view_surface.layoutParams as RelativeLayout.LayoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = ViewGroup.LayoutParams.MATCH_PARENT
view_surface.layoutParams = params
} else {
val params = view_surface.layoutParams as RelativeLayout.LayoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height =
(240 * requireContext().applicationContext.resources.displayMetrics.density).toInt()
view_surface.layoutParams = params
videoControlWidget.show()
videoFullViewWidget.hide()
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.d("ORIENTAIONNNNNN", "ORIENTATIONCHANGE")
requireActivity().let {
if (isChangeScreenButtonClicked) {
Log.d("ORIENTAIONNNNNN", "BUTTONCLICKED")
stopAutoOrientationFor3Seconds()
} else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Log.d("ORIENTAIONNNNNN", "LANDSCAPE")
onFullScreenListener.onFullSreen(false)
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
showVideoView(true)
isPortraitMode = false
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
Log.d("ORIENTAIONNNNNN", "POTRIAT")
onFullScreenListener.onFullSreen(true)
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
showVideoView(false)
isPortraitMode = true
}
}
}
private fun stopAutoOrientationFor3Seconds() {
val handler = Handler()
handler.postDelayed(object : Runnable {
override fun run() {
run {
isChangeScreenButtonClicked = false
activity?.let {
it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
}
}, 5000)
}
}
This is my Activity call back
override fun onFullSreen(fullscreen: Boolean) {
if (fullscreen) {
nav_view.visibility = GONE
window.decorView.systemUiVisibility = (android.view.View.SYSTEM_UI_FLAG_IMMERSIVE
or android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (supportActionBar != null) {
supportActionBar!!.hide()
}
Log.d("ORIENTAIONNNNNN", "FULLVIEW")
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
nav_view.visibility = VISIBLE
Log.d("ORIENTAIONNNNNN", "HALFVIEW")
window.decorView.systemUiVisibility = android.view.View.SYSTEM_UI_FLAG_VISIBLE
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (supportActionBar != null) {
supportActionBar!!.show()
}
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
}
This is my fragment i have button click for full screen and also screen rotation on sensor on button click its working fine i am able to rotate screen because we are not depending on onConfigurationChanged method while when rotate device manually then first time when i launch app and try to rotate then portrait to landscape then onConfigurationChanged method calling but when i try to rotate to landscape to portrait then onConfigurationChanged method is not calling please help me what i am doing wrong i am following mvvm pattern .
Sorry it was my mistake i got solution for this question .
when i move it.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR this line of code at end in fragment onconfig change method its work fine
Related
I have this app in which I have a parent fragment, which has 2 child fragments in a ViewPager2 object.
One of the child fragments has an interface to communicate changes on its menu to the parent fragment.
The child fragment in question is TasksListFragment.kt and the parent fragment is TodayFragment.kt
When I try to initialize the interface in the child fragment onAttach() function, I get
FATAL EXCEPTION: main Process: com.rajchenbergstudios.hoygenda, PID:
java.lang.ClassCastException: com.rajchenbergstudios.hoygenda.ui.activity.MainActivity cannot be cast to com.rajchenbergstudios.hoygenda.ui.todaylists.taskslist.TasksListFragment$ChildFragmentListener
I don't understand why I get this error, and it says MainActivity, when the parent is a fragment which is the one implementing the interface in the first place, not the MainActivity.
I have everything set up correctly:
I have an interface in the child fragment
The interface is used on the child fragment onCreateMenu to pass the menu object to its interface function onFragmentMenuChanged(menu: Menu)
I override the child fragment's onAttach() and initialize the interface:
override fun onAttach(context: Context) {
super.onAttach(context)
childFragmentListener = context as ChildFragmentListener
}
I write a function called setListener() which is called from the parent fragment to pass its context this to the function parameter which assigns it to the childFragment listener
fun setListener(listener: ChildFragmentListener) {
this.childFragmentListener = listener
}
The parent fragment implements the child fragment listener as seen in the TodayFragment.kt file
Can you tell me what am I doing wrong or how to implement an interface to effectively communicate from child fragment back to its parent fragment?
TasksListFragment.kt
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class TasksListFragment : Fragment(R.layout.fragment_child_tasks_list), TasksListAdapter.OnItemClickListener {
private val viewModel: TasksListViewModel by viewModels()
private lateinit var searchView: SearchView
private lateinit var childFragmentListener: ChildFragmentListener
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentChildTasksListBinding.bind(view)
val tasksListAdapter = TasksListAdapter(this)
binding.apply {
tasksListRecyclerview.layoutTasksListRecyclerview.apply {
adapter = tasksListAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
}
ItemTouchHelper(object: ItemTouchHelper.SimpleCallback(0,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val task = tasksListAdapter.currentList[viewHolder.adapterPosition]
viewModel.onTaskSwiped(task)
}
}).attachToRecyclerView(tasksListRecyclerview.layoutTasksListRecyclerview)
}
loadObservable(binding, tasksListAdapter)
loadTasksEventCollector()
loadMenu()
}
private fun loadObservable(binding: FragmentChildTasksListBinding, tasksListAdapter: TasksListAdapter) {
viewModel.tasks.observe(viewLifecycleOwner){ tasksList ->
binding.apply {
HGDAViewStateUtils.apply {
if (tasksList.isEmpty()) {
setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.INVISIBLE)
setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.VISIBLE)
} else {
setViewVisibility(tasksListRecyclerview.layoutTasksListRecyclerview, visibility = View.VISIBLE)
setViewVisibility(tasksListLayoutNoData.layoutNoDataLinearlayout, visibility = View.INVISIBLE)
tasksListAdapter.submitList(tasksList)
}
}
}
}
}
/**
* TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage: Stays in this class. It asks for components relevant to this class.
* TasksListViewModel.TaskEvent.NavigateToEditTaskScreen: Stays in this class. The method it overrides comes from task list adapter.
* TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen: Stays in this class. Relevant to menu which is in this class.
* TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen: Stays in this class. Relevant to menu which is in this class.
*/
private fun loadTasksEventCollector() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.tasksEvent.collect { event ->
when (event) {
is TasksListViewModel.TaskEvent.ShowUndoDeleteTaskMessage -> {
Snackbar
.make(requireView(), "Task deleted", Snackbar.LENGTH_LONG)
.setAction("UNDO"){
viewModel.onUndoDeleteClick(event.task)
}
.show()
}
is TasksListViewModel.TaskEvent.NavigateToEditTaskScreen -> {
val action = TodayFragmentDirections
.actionTodayFragmentToTaskAddEditFragment(task = event.task, title = "Edit task", taskinset = null, origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToAddTaskToSetBottomSheet -> {
val action = TasksListFragmentDirections.actionGlobalSetBottomSheetDialogFragment(task = event.task, origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToDeleteAllCompletedScreen -> {
val action = TasksListFragmentDirections
.actionGlobalTasksDeleteAllDialogFragment(origin = 1)
findNavController().navigate(action)
}
is TasksListViewModel.TaskEvent.NavigateToDeleteAllScreen -> {
val action = TasksListFragmentDirections
.actionGlobalTasksDeleteAllDialogFragment(origin = 3)
findNavController().navigate(action)
}
}.exhaustive
}
}
}
private fun loadMenu(){
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(object: MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
childFragmentListener.onFragmentChanged(menu)
menuInflater.inflate(R.menu.menu_tasks_list_fragment, menu)
val searchItem = menu.findItem(R.id.tasks_list_menu_search)
searchView = searchItem.actionView as SearchView
val pendingQuery = viewModel.searchQuery.value
if (pendingQuery != null && pendingQuery.isNotEmpty()) {
searchItem.expandActionView()
searchView.setQuery(pendingQuery, false)
}
searchView.OnQueryTextChanged{ searchQuery ->
viewModel.searchQuery.value = searchQuery
}
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
menu.findItem(R.id.tasks_list_menu_hide_completed).isChecked =
viewModel.preferencesFlow.first().hideCompleted
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.tasks_list_menu_sort_by_date -> {
viewModel.onSortOrderSelected(SortOrder.BY_DATE)
true
}
R.id.tasks_list_menu_sort_by_name -> {
viewModel.onSortOrderSelected(SortOrder.BY_NAME)
true
}
R.id.tasks_list_menu_hide_completed -> {
menuItem.isChecked = !menuItem.isChecked
viewModel.onHideCompletedSelected(menuItem.isChecked)
true
}
R.id.tasks_list_menu_delete_completed -> {
viewModel.onDeleteAllCompletedClick()
true
}
R.id.tasks_list_menu_delete_all -> {
viewModel.onDeleteAllClick()
true
}
else -> false
}
}
}, viewLifecycleOwner, Lifecycle.State.RESUMED)
}
interface ChildFragmentListener {
fun onFragmentChanged(menu: Menu)
}
fun setListener(listener: ChildFragmentListener) {
this.childFragmentListener = listener
}
override fun onItemClick(task: Task) {
viewModel.onTaskSelected(task)
}
override fun onItemLongClick(task: Task) {
viewModel.onTaskLongSelected(task)
}
override fun onCheckboxClick(task: Task, isChecked: Boolean) {
viewModel.onTaskCheckedChanged(task, isChecked)
}
override fun onAttach(context: Context) {
super.onAttach(context)
childFragmentListener = context as ChildFragmentListener
}
override fun onPause() {
super.onPause()
Logger.i(TAG, "onPause", "TasksListFragment paused")
}
override fun onDestroyView() {
super.onDestroyView()
searchView.setOnQueryTextListener(null)
}
}
TodayFragment.kt
#ExperimentalCoroutinesApi
#AndroidEntryPoint
class TodayFragment : Fragment(R.layout.fragment_parent_today), TasksListFragment.ChildFragmentListener {
private val viewModel: TodayViewModel by viewModels()
private lateinit var binding: FragmentParentTodayBinding
private var fabClicked: Boolean = false
private lateinit var tasksListMenu: Menu
private lateinit var viewPager: ViewPager2
private val rotateOpen: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_open_anim) }
private val rotateClose: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.rotate_close_anim) }
private val fromBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.from_bottom_anim) }
private val toBottom: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.to_bottom_anim) }
private val fadeIn: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_in) }
private val fadeOut: Animation by lazy { AnimationUtils.loadAnimation(requireContext(), R.anim.fade_out) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = FragmentParentTodayBinding.bind(view)
binding.apply {
tasksListTransparentWhiteScreen.setOnClickListener {
fabAnimationsRollBack(binding)
fabClicked = !fabClicked
}
}
setChildFragmentMenus()
initViewPagerWithTabLayout(binding)
todayDateDisplay(binding)
initFabs(binding)
loadTodayEventCollector()
getFragmentResultListeners()
}
private fun setChildFragmentMenus(){
val tasksListFragment = TasksListFragment()
tasksListFragment.setListener(this)
Logger.i(TAG, "setChildFragmentMenus", "TasksListFragment menu set")
}
private fun getFragmentResultListeners() {
setFragmentResultListener("add_edit_request"){_, bundle ->
val result = bundle.getInt("add_edit_result")
onFragmentResult(result)
}
setFragmentResultListener("create_set_request_2"){_, bundle ->
val result = bundle.getInt("create_set_result_2")
onFragmentResult(result)
}
setFragmentResultListener("task_added_to_set_request"){_, bundle ->
val result = bundle.getInt("task_added_to_set_result")
val message = bundle.getString("task_added_to_set_message")
onFragmentResult(result, message)
}
setFragmentResultListener("task_added_from_set_request"){_, bundle ->
val result = bundle.getInt("task_added_from_set_result")
val message = bundle.getString("task_added_from_set_message")
onFragmentResult(result, message)
}
}
private fun onFragmentResult(result: Int, message: String? = ""){
viewModel.onFragmentResult(result, message)
}
/**
* TodayViewModel.TodayEvent.NavigateToAddTaskScreen: Relevant to this class. Belongs to Fab which are all in this class.
* TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage: Relevant to this class. Belongs to onFragmentResultListener which is here.
* TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet: Relevant to this class. Belongs to Fab which are all in this class.
*/
private fun loadTodayEventCollector() {
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.todayEvent.collect { event ->
when (event) {
is TodayViewModel.TodayEvent.NavigateToAddTaskScreen -> {
val action = TodayFragmentDirections
.actionTodayFragmentToTaskAddEditFragment(task = null, title = "Add task"
, taskinset = null, origin = 1)
findNavController().navigate(action)
}
is TodayViewModel.TodayEvent.ShowTaskSavedConfirmationMessage -> {
Snackbar.make(requireView(), event.msg, Snackbar.LENGTH_LONG).show()
setViewPagerPage(0)
}
is TodayViewModel.TodayEvent.ShowTaskSavedInNewOrOldSetConfirmationMessage -> {
Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
}
is TodayViewModel.TodayEvent.ShowTaskAddedFromSetConfirmationMessage -> {
Snackbar.make(requireView(), event.msg.toString(), Snackbar.LENGTH_LONG).show()
fabClicked = true
setFabAnimationsAndViewStates(binding)
setViewPagerPage(0)
}
is TodayViewModel.TodayEvent.NavigateToAddTasksFromSetBottomSheet -> {
val action = TasksListFragmentDirections
.actionGlobalSetBottomSheetDialogFragment(task = null, origin = 2)
findNavController().navigate(action)
}
}.exhaustive
}
}
}
// This will soon be used to be 1
private fun setViewPagerPage(index: Int){
viewModel.postActionWithDelay(300, object: TodayViewModel.PostActionListener{
override fun onDelayFinished() {
viewPager.setCurrentItem(index, true)
}
})
}
private fun todayDateDisplay(binding: FragmentParentTodayBinding) {
binding.apply {
tasksListDateheader.apply {
dateHeaderDayofmonth.text = viewModel.getCurrentDayOfMonth()
dateHeaderMonth.text = viewModel.getCurrentMonth()
dateHeaderYear.text = viewModel.getCurrentYear()
dateHeaderDayofweek.text = viewModel.getCurrentDayOfWeek()
}
}
}
private fun initViewPagerWithTabLayout(binding: FragmentParentTodayBinding) {
viewPager = binding.todayViewpager
val tabLayout: TabLayout = binding.todayTablayout
viewPager.adapter = activity?.let { TodayPagerAdapter(it) }
Logger.i(TAG, "initViewPagerWithTabLayout", "viewPager is not null")
TabLayoutMediator(tabLayout, viewPager) { tab, index ->
tab.text = when (index) {
0 -> "Tasks"
1 -> "Journal"
else -> throw Resources.NotFoundException("Tab not found at position")
}.exhaustive
when (index) {
0 -> {
}
1 -> {
fabClicked = false
}
}
}.attach()
}
private fun initFabs(binding: FragmentParentTodayBinding) {
binding.apply {
tasksListFab.setOnClickListener {
onMainFabClick(binding)
}
tasksListSubFab1.setOnClickListener {
Logger.i(TAG, "initFabs", "Coming soon")
}
tasksListSubFab2.setOnClickListener {
viewModel.onAddTasksFromSetClick()
}
tasksListSubFab3.setOnClickListener {
viewModel.onAddNewTaskClick()
}
}
}
private fun onMainFabClick(binding: FragmentParentTodayBinding) {
setFabAnimationsAndViewStates(binding)
}
private fun setFabAnimationsAndViewStates(binding: FragmentParentTodayBinding) {
setFabAnimationVisibilityAndClickability(binding, fabClicked)
fabClicked = !fabClicked
}
private fun setFabAnimationVisibilityAndClickability(binding: FragmentParentTodayBinding, clicked: Boolean) {
if (!clicked) fabAnimationsRollIn(binding) else fabAnimationsRollBack(binding)
}
private fun fabAnimationsRollIn(binding: FragmentParentTodayBinding) {
binding.apply {
HGDAAnimationUtils.apply {
HGDAViewStateUtils.apply {
setViewAnimation(v1 = tasksListFab, a = rotateOpen)
setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = fromBottom)
setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = fromBottom)
setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeIn)
setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
, v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.VISIBLE)
setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.VISIBLE)
setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = true)
setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = true)
}
}
}
}
private fun fabAnimationsRollBack(binding: FragmentParentTodayBinding) {
binding.apply {
HGDAAnimationUtils.apply {
HGDAViewStateUtils.apply {
setViewAnimation(v1 = tasksListFab, a = rotateClose)
setViewAnimation(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, a = toBottom)
setViewAnimation(v1 = tasksListSubFab1Tv, v2 = tasksListSubFab2Tv, v3 = tasksListSubFab3Tv, a = toBottom)
setViewAnimation(v1 = tasksListTransparentWhiteScreen, a = fadeOut)
setViewVisibility(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3
, v4 = tasksListSubFab1Tv, v5 = tasksListSubFab2Tv, v6 = tasksListSubFab3Tv, visibility = View.INVISIBLE)
setViewVisibility(v1 = tasksListTransparentWhiteScreen, visibility = View.INVISIBLE)
setViewClickState(v1 = tasksListSubFab1, v2 = tasksListSubFab2, v3 = tasksListSubFab3, clickable = false)
setViewClickState(v1 = tasksListTransparentWhiteScreen, clickable = false)
}
}
}
}
override fun onFragmentChanged(menu: Menu) {
tasksListMenu = menu
}
override fun onPause() {
super.onPause()
tasksListMenu.clear()
}
}
Fragment is not a Context i.e fragment is not a child of context .
So when you try to cast context as ChildFragmentListener you are actually casting your Activity to ChildFragmentListener which is giving you this RuntimeException . to make it work you can use childFragmentListener = parentFragment as ChildFragmentListener
Also if your Doing this you do not need setListener method anymore.
On other hand i would suggest you do not use listeners to communicate b/w fragments . I see you already using viewModel so just use a shared one to communicate . You can get a shared ViewModel inside child by creating it with parentFragment.
I was developing an app in kotlin, and I try to make a custom dialog, which extends of my parent class BaseDialogFragment, and when I try to instace my GenericDialog I get the following error:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: es.renaultbank.andr, PID: 21498
java.lang.IllegalStateException: Fragment GenericDialog{a0c2f1e} (ffc56cd1-3856-466f-8705-c66209c24963 tag=sucess_otp_dialog) did not return a View from onCreateView() or this was called before onCreateView().
at androidx.fragment.app.Fragment.requireView(Fragment.java:1964)
at es.renaultbank.andr.utils.GenericDialog.configureEvents(GenericDialog.kt:39)
at es.renaultbank.andr.ui.customviews.dialog.BaseDialogFragment.onCreateView(BaseDialogFragment.kt:17)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2963)
at androidx.fragment.app.DialogFragment.performCreateView(DialogFragment.java:489)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:518)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
My GenericDialog class is the following:
package es.renaultbank.andr.utils
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import es.renaultbank.andr.R
import es.renaultbank.andr.ui.customviews.dialog.BaseDialogFragment
import es.renaultbank.andr.ui.extensions.find
class GenericDialog(
context: Context,
private val icon: Int? = null,
private val title: String? = null,
private val subtitle: String? = null,
private val buttonText: String? = null,
private var clickListener: View.OnClickListener? = null,
private val cancelable: Boolean = true
) : BaseDialogFragment() {
private var iv_icon: ImageView = ImageView(context)
private var tv_title: TextView = TextView(context)
private var tv_subtitle: TextView = TextView(context)
private var btn_action: Button = Button(context)
private var iv_close: ImageView = ImageView(context)
override fun configureView(view: View?) {
dialog?.setCancelable(true)
dialog?.setCanceledOnTouchOutside(true)
}
override fun configureEvents(view: View?) {
iv_icon = requireView().findViewById(R.id.iv_icon)
tv_title = requireView().findViewById(R.id.tv_title)
tv_subtitle = requireView().findViewById(R.id.tv_subtitle)
btn_action = requireView().findViewById(R.id.btn_action)
iv_close = requireView().findViewById(R.id.iv_close)
}
override fun getLayout(): Int {
return R.layout.dialog_generic
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setView()
setListeners()
}
fun setIcon(icon: Int) {
iv_icon.setImageDrawable(requireContext().getDrawable(icon))
iv_icon.visibility = View.VISIBLE
iv_icon.setColorFilter(R.color.black)
}
fun setTitle(title: String) {
tv_title.text = title
tv_title.visibility = View.VISIBLE
}
fun setSubtitle(subtitle: String) {
tv_subtitle.text = subtitle
tv_subtitle.visibility = View.VISIBLE
}
fun setButtonText(text: String) {
btn_action.text = text
btn_action.visibility = View.VISIBLE
}
fun setOnClickListener() {
btn_action.setOnClickListener { clickListener }
}
override fun setCancelable(flag: Boolean) {
super.setCancelable(flag)
if (flag) {
iv_close.visibility = View.VISIBLE
} else {
iv_close.visibility = View.GONE
}
}
private fun setView() {
if (icon != null) {
setIcon(icon)
} else {
iv_icon.visibility = View.GONE
}
if (title != null) {
setTitle(title)
} else {
tv_title.visibility = View.GONE
}
if (subtitle != null) {
setSubtitle(subtitle)
} else {
tv_subtitle.visibility = View.GONE
}
if (buttonText != null) {
setButtonText(buttonText)
} else {
btn_action.visibility = View.GONE
}
setCancelable(cancelable)
}
fun dismissDialog(){
this.dismiss()
}
private fun setListeners() {
iv_close.setOnClickListener {
dismiss()
}
if (clickListener != null) {
btn_action.setOnClickListener(clickListener)
} else {
btn_action.setOnClickListener {
dismiss()
}
}
}
}
and their parent class is this:
BaseDialogFragment.kt
package es.renaultbank.andr.ui.customviews.dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.fragment.app.DialogFragment
abstract class BaseDialogFragment : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView: View = inflater.inflate(getLayout(), container, false)
configureView(rootView)
configureEvents(rootView)
return rootView
}
fun hideKeyboard(view: View?) {
view?.let {
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}
/**
* Configuracion de los findViewById la vista, colores, animaciones...
*/
protected abstract fun configureView(view: View?)
/**
* Configuracion de listeners
*/
protected abstract fun configureEvents(view: View?)
/**
* Setea el layout del dialog
*/
protected abstract fun getLayout(): Int
}
Finally the used of the GenericDialog is a simple network call, and if sucess, show the dialog
if(it.errorBase.getErrorType() == ErrorBase.ErrorType.NON_ERROR) {
(it.responseBase).let {resp ->
if ((resp as ValidatePassOTPResponse).response == "200 OK") {
val sucessDialog = GenericDialog(
context = requireContext(),
icon = R.drawable.ic_tick_green,
title = getString(R.string.change_password_title),
subtitle = getString(R.string.password_change_sucess),
buttonText = getString(R.string.understand),
cancelable = true,
clickListener = { (activity as DashboarActivity).redirectToLogin() }
)
sucessDialog.show(requireFragmentManager(), "sucess_otp_dialog")
} else {
showOtpError().also {
(activity as DashboarActivity).redirectToLogin()
}
}
}
}else{
(activity as BaseActivity<*>).showError(it.errorBase, null)
dialog?.dismiss().also {
(activity as DashboarActivity).redirectToLogin()
}
}
})
I hope you can help, in case you've faced some similar problem.
Take thanks in advance !
[EDIT]
I've taken to show the dialog replacing requireView to view, in the configureEvents method. but althought is shown and debugging I see that the set method in the dialog are invoked, the dialog show like that:
As you can check is empty although the params which I'm passing are not empty.
Call setView() and setListener() method inside configureEvents() method after initializing the views.
Refer the following code
class GenericDialog : BaseDialogFragment() {
private lateinit var iv_icon: ImageView
private lateinit var tv_title: TextView
private lateinit var tv_subtitle: TextView
private lateinit var btn_action: Button
private lateinit var iv_close: ImageView
override fun configureView(view: View?) {
dialog?.setCancelable(true)
dialog?.setCanceledOnTouchOutside(true)
}
override fun configureEvents(view: View?) {
iv_icon = requireView().findViewById(R.id.iv_icon)
tv_title = requireView().findViewById(R.id.tv_title)
tv_subtitle = requireView().findViewById(R.id.tv_subtitle)
btn_action = requireView().findViewById(R.id.btn_action)
iv_close = requireView().findViewById(R.id.iv_close)
setView()
setListeners()
}
override fun getLayout(): Int {
return R.layout.dialog_generic
}
private fun setIcon(icon: Int) {
iv_icon.setImageDrawable(requireContext().getDrawable(icon))
iv_icon.visibility = View.VISIBLE
iv_icon.setColorFilter(R.color.black)
}
private fun setTitle(title: String) {
tv_title.text = title
tv_title.visibility = View.VISIBLE
}
private fun setSubtitle(subtitle: String) {
tv_subtitle.text = subtitle
tv_subtitle.visibility = View.VISIBLE
}
private fun setButtonText(text: String) {
btn_action.text = text
btn_action.visibility = View.VISIBLE
}
private fun setView() {
val icon = requireArguments().getInt(ICON, -1)
val title = requireArguments().getString(TITLE)
val subtitle = requireArguments().getString(SUB_TITLE)
val buttonText = requireArguments().getString(BUTTON_TEXT)
val cancelable = requireArguments().getBoolean(CANCELABLE)
if (icon != -1) {
setIcon(icon)
} else {
iv_icon.visibility = View.GONE
}
if (title != null) {
setTitle(title)
} else {
tv_title.visibility = View.GONE
}
if (subtitle != null) {
setSubtitle(subtitle)
} else {
tv_subtitle.visibility = View.GONE
}
if (buttonText != null) {
setButtonText(buttonText)
} else {
btn_action.visibility = View.GONE
}
setCancelable(cancelable)
}
fun dismissDialog() {
this.dismiss()
}
private fun setListeners() {
iv_close.setOnClickListener {
dismiss()
}
btn_action.setOnClickListener {
val clickListener = getActionListener()
if (clickListener != null) {
clickListener.onAction()
} else {
dismiss()
}
}
}
private fun getActionListener(): ActionListener? {
val activity = this.activity
return if (activity is ActionListener) activity
else null
}
companion object {
private const val ICON = "ICON"
private const val TITLE = "TITLE"
private const val SUB_TITLE = "SUB_TITLE"
private const val BUTTON_TEXT = "BUTTON_TEXT"
private const val CANCELABLE = "cancelable"
fun newInstance(
icon: Int? = null,
title: String? = null,
subtitle: String? = null,
buttonText: String? = null,
cancelable: Boolean = true) = GenericDialog().apply {
arguments = Bundle().apply {
putString(TITLE, title)
putString(SUB_TITLE, subtitle)
putString(BUTTON_TEXT, buttonText)
if (icon != null) {
putInt(ICON, icon)
}
putBoolean(CANCELABLE, cancelable)
}
}
}
interface ActionListener {
fun onAction()
}
}
I have a ViewHolder in my recyclerView that implements Toroplayer:
internal class CommunityPlayableViewHolder(itemView: View) : FeedViewHolder(itemView), ToroPlayer, ToroPlayer.EventListener, ToroPlayer.OnErrorListener {
private val exoPlayerView: PlayerView = itemView.findViewById(R.id.toroPlayerView)
private val volumeView: AppCompatImageView = itemView.findViewById(R.id.volumeIv)
private val audioManager = itemView.context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
private var helper: ExoPlayerViewHelper? = null
private var videoUrl: String? = null
fun bindCommunityVideoOffer(model: CommunityOffer) {
Timber.d("PlayableHolder: binding video with url:${model.mediaVideoUriDash}")
}
private fun toggleVolume(volumeView: AppCompatImageView) {
exoPlayerView.player?.let {
if (helper?.volume == 0f && requestAudioFocus()) {
helper?.volume = 1f
volumeView.setImageResource(R.drawable.ic_volume_on)
} else {
helper?.volume = 0f
releaseAudioFocus()
volumeView.setImageResource(R.drawable.ic_volume_off)
}
}
}
private fun requestAudioFocus(): Boolean {
val result = audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
}
private fun releaseAudioFocus() {
audioManager.abandonAudioFocus(null)
}
private fun showPlayer(showVolume: Boolean) {
Timber.d("PlayableHolder: showPlayer() called")
exoPlayerView.show()
if (showVolume) volumeView.show() else volumeView.hide()
}
override fun getPlayerView() = this.exoPlayerView
override fun getCurrentPlaybackInfo(): PlaybackInfo {
Timber.d("PlayableHolder: getCurrentPlaybackInfo() called")
return helper?.latestPlaybackInfo ?: PlaybackInfo()
}
override fun initialize(container: Container, playbackInfo: PlaybackInfo) {
Timber.d("PlayableHolder: initialized() called")
videoUrl?.let { url ->
(helper
?: ExoPlayerViewHelper(this, Uri.parse(url)).also {
helper = it
}).initialize(container, playbackInfo)
helper?.addPlayerEventListener(this)
helper?.addErrorListener(this)
val isMute = currentPlaybackInfo.volumeInfo.isMute
volumeView.setImageResource(if (isMute) R.drawable.ic_volume_off else R.drawable.ic_volume_on)
}
}
override fun play() {
Timber.d("PlayableHolder: play() called")
helper?.play()
}
override fun pause() {
Timber.d("PlayableHolder: pause() called")
helper?.pause()
}
override fun isPlaying(): Boolean {
Timber.d("PlayableHolder: isPlaying() called")
return helper?.isPlaying ?: false
}
override fun release() {
Timber.d("PlayableHolder: release() called")
exoPlayerView.unshow()
communityPlayableIv.show()
helper?.removePlayerEventListener(this)
helper?.removeErrorListener(this)
helper?.release()
helper = null
}
override fun wantsToPlay(): Boolean {
Timber.d("PlayableHolder: wantsToPlay() called")
val parent = itemView.parent as Container
if (isLastPlayableItem(parent)) {
parent.playerSelector = PlayerSelector.DEFAULT_REVERSE
return ToroUtil.visibleAreaOffset(this, parent) >= 0.8f
} else {
parent.playerSelector = PlayerSelector.DEFAULT
}
val visibleAreaOffset = if (countPlayableItems(parent) <= 1) 0.2f else 0.8f
return ToroUtil.visibleAreaOffset(this, itemView.parent) >= visibleAreaOffset
}
override fun getPlayerOrder() = adapterPosition
override fun onBuffering() {
Timber.d("PlayableHolder: onBuffering() called")
exoPlayerView.player?.repeatMode = Player.REPEAT_MODE_ONE
}
override fun onFirstFrameRendered() {
Timber.d("PlayableHolder: firstFrameRendered() called")
communityPlayableIv.unshow()
}
override fun onPlaying() {
Timber.d("PlayableHolder: onPlaying() called")
SDKEventUtil.CommunityTiles.trackCommunityTileVideoStarted(communityTileId
?: "", type?.name?.toLowerCase() ?: "")
val hasAudio = isAudioTrackAvailable(exoPlayerView.player)
showPlayer(hasAudio)
volumeView.setOnClickListener { toggleVolume(it as AppCompatImageView) }
}
override fun onPaused() {
Timber.d("PlayableHolder: onPaused() called")
volumeView.setOnClickListener(null)
}
override fun onCompleted() {
Timber.d("PlayableHolder: onCompleted() called")
SDKEventUtil.CommunityTiles.trackCommunityTileVideoFinished(communityTileId
?: "", type?.name?.toLowerCase() ?: "")
}
override fun onError(error: Exception?) {
Timber.d("PlayableHolder: onError() called")
SDKEventUtil.CommunityTiles.trackCommunityTileVideoFailed(communityTileId
?: "", type?.name?.toLowerCase() ?: "")
}
}
in xml:
<com.google.android.exoplayer2.ui.PlayerView
android:id="#+id/toroPlayerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#color/transparent"
android:visibility="invisible"
app:controller_layout_id="#layout/v_player_controls"
app:keep_content_on_player_reset="true"
app:layout_constraintBottom_toTopOf="#id/actionBarPlayable"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/communityPlayableIv"
app:player_layout_id="#layout/toro_exo_player_view"
app:resize_mode="zoom"
app:show_buffering="when_playing"
app:shutter_background_color="#android:color/transparent"
app:surface_type="texture_view"
app:use_controller="false" />
The video is not playing, and the initialize() method from Toroplayer is not even being called. The bind method is This has happened spontaneously, and as far as I can tell this ViewHolder is indistinguishable from a previous one I recalled from git which works. I have never updated Toroplayer from
implementation "im.ene.toro3:toro:3.7.0.2905-A1"
implementation "im.ene.toro3:toro-ext-exoplayer:3.7.0.2905-A1"
Nor have I updated my Exoplayer dependencies
implementation("com.google.android.exoplayer:exoplayer:'2.10.4') {
exclude group: 'com.android.support'
}
implementation(
"com.google.android.exoplayer:extension-ima:2.10.4") {
exclude group: 'com.android.support'
}
Is there anything wrong with my implementation here? Why does this not work?
My recyclerView containing the video items was initialized as a RecyclerView instead of the Container class that comes with the Toro library, and I wasn't calling setPlayerInitializer on that view in my Fragment
I have following project in Github : https://github.com/AliRezaeiii/TMDb-Paging
I have to postDelay calling methods in my ViewModel since datasource is not initialized :
abstract class DetailViewModel(private val item: TmdbItem) : BaseViewModel() {
private val handler = Handler(Looper.getMainLooper())
val trailers: ObservableList<Video> = ObservableArrayList()
val isTrailersVisible = ObservableBoolean(false)
private val _cast = MutableLiveData<List<Cast>>()
val cast: LiveData<List<Cast>> = _cast
val isCastVisible = ObservableBoolean(false)
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
protected abstract fun getTrailers(id: Int): Observable<List<Video>>
protected abstract fun getCast(id: Int): Observable<List<Cast>>
private fun showTrailers() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getTrailers(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ videos ->
if (videos.isNotEmpty()) {
isTrailersVisible.set(true)
}
with(trailers) {
clear()
addAll(videos)
}
}
) { throwable -> Timber.e(throwable) })
}
private fun showCast() {
EspressoIdlingResource.increment() // App is busy until further notice
compositeDisposable.add(getCast(item.id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
if (!EspressoIdlingResource.getIdlingResource().isIdleNow) {
EspressoIdlingResource.decrement() // Set app as idle.
}
}
.subscribe({ cast ->
if (cast.isNotEmpty()) {
isCastVisible.set(true)
}
this._cast.postValue(cast)
}
) { throwable -> Timber.e(throwable) })
}
}
And here is my Fragment :
abstract class DetailFragment<T : TmdbItem>
: BaseDaggerFragment(), CastClickCallback {
protected abstract fun getViewModel(): DetailViewModel
protected abstract fun getLayoutId(): Int
protected abstract fun initViewBinding(root: View): ViewDataBinding
protected abstract fun getTmdbItem(): T
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val viewModel = getViewModel()
val root = inflater.inflate(getLayoutId(), container, false)
initViewBinding(root).apply {
setVariable(BR.vm, viewModel)
lifecycleOwner = viewLifecycleOwner
}
with(root) {
with(activity as AppCompatActivity) {
setupActionBar(details_toolbar) {
setDisplayShowTitleEnabled(false)
setDisplayHomeAsUpEnabled(true)
setDisplayShowHomeEnabled(true)
}
}
summary_label.visibleGone(getTmdbItem().overview.trim().isNotEmpty())
// Make the MotionLayout draw behind the status bar
details_motion.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
summary.setOnClickListener {
val maxLine = resources.getInteger(R.integer.max_lines)
summary.maxLines = if (summary.maxLines > maxLine) maxLine else Int.MAX_VALUE
}
viewModel.cast.observe(viewLifecycleOwner, Observer {
it?.apply {
val adapter = CastAdapter(it, this#DetailFragment)
cast_list.apply {
setHasFixedSize(true)
cast_list.adapter = adapter
}
}
})
with(details_rv) {
postDelayed({ scrollTo(0, 0) }, 100)
}
}
return root
}
}
And BaseDaggerFragment :
open class BaseDaggerFragment : DaggerFragment() {
#Inject
lateinit var dataSource: RemoteDataSource
}
Could be any better solution than :
init {
handler.postDelayed({
showTrailers()
showCast()
}, 100)
}
You can lazy initialize like this way
private val users:MutableLiveData<List<Cast>> by lazy {
MutableLiveData().also {
showTrailers()
showCast()
}
}
more details refer ViewModel
While writing an Android app, I encountered a problem with a stuttering animation. I use AHBottomNavigation for navigation, FragNav is for swapping fragments and FlexibleAdapter for RecyclerView.
The application is built from one activity and five fragments. When I try to switch to the first fragment in the application, the BottomNavigation animation freez for a moment. It looks very unsightly. The second time I choose the same fragment, everything works smoothly. It seems to me that it is the fault to initialize the views in the fragment, but I have no idea how to do it differently.
AHBottomNavigation https://github.com/aurelhubert/ahbottomnavigation
FragNav https://github.com/ncapdevi/FragNav
FlexibleAdapter https://github.com/davideas/FlexibleAdapter
Fragment
class GradeFragment : BaseFragment(), GradeView {
#Inject
lateinit var presenter: GradePresenter
private val gradeAdapter = FlexibleAdapter<AbstractFlexibleItem<*>>(null, null, true)
companion object {
fun newInstance() = GradeFragment()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_grade, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
presenter.run {
attachView(this#GradeFragment)
loadData()
}
}
override fun initView() {
gradeAdapter.run {
isAutoCollapseOnExpand = true
isAutoScrollOnExpand = true
setOnUpdateListener { presenter.onUpdateDataList(it) }
setOnItemClickListener { position ->
getItem(position).let {
if (it is GradeItem) {
GradeDialog.newInstance(it.grade).show(fragmentManager, it.toString())
}
}
}
}
gradeRecycler.run {
layoutManager = SmoothScrollLinearLayoutManager(context)
adapter = gradeAdapter
}
gradeSwipe.setOnRefreshListener { presenter.loadData(forceRefresh = true) }
}
override fun updateData(data: List<GradeHeader>) {
gradeAdapter.updateDataSet(data, true)
}
override fun isViewEmpty(): Boolean = gradeAdapter.isEmpty
override fun showEmptyView(show: Boolean) {
gradeEmpty.visibility = if (show) VISIBLE else GONE
}
override fun showProgress(show: Boolean) {
gradeProgress.visibility = if (show) VISIBLE else GONE
}
override fun setRefresh(show: Boolean) {
gradeSwipe.isRefreshing = show
}
Presenter
class GradePresenter #Inject constructor(
private val errorHandler: ErrorHandler,
private val schedulers: SchedulersManager,
private val gradeRepository: GradeRepository,
private val sessionRepository: SessionRepository) : BasePresenter<GradeView>(errorHandler) {
override fun attachView(view: GradeView) {
super.attachView(view)
view.initView()
}
fun loadData(forceRefresh: Boolean = false) {
disposable.add(sessionRepository.getSemesters()
.map { it.single { semester -> semester.current } }
.flatMap { gradeRepository.getGrades(it, forceRefresh) }
.map { it.groupBy { grade -> grade.subject } }
.map { createGradeItems(it) }
.subscribeOn(schedulers.backgroundThread())
.observeOn(schedulers.mainThread())
.doFinally { view?.setRefresh(false) }
.doOnSuccess { if (it.isEmpty()) view?.showEmptyView(true) }
.doOnError { view?.run { if (isViewEmpty()) showEmptyView(true) } }
.subscribe({ view?.updateData(it) }) { errorHandler.proceed(it) })
}
private fun createGradeItems(items: Map<String, List<Grade>>): List<GradeHeader> {
return items.map {
val gradesAverage = calcAverage(it.value)
GradeHeader().apply {
subject = it.key
average = view?.run {
if (gradesAverage == 0f) emptyAverageString()
else averageString().format(gradesAverage)
}.orEmpty()
number = view?.gradeNumberString(it.value.size).orEmpty()
subItems = (it.value.map { item ->
GradeItem().apply {
grade = item
weightString = view?.weightString().orEmpty()
valueColor = getValueColor(item.value)
}
})
}
}
}
fun onUpdateDataList(size: Int) {
if (size != 0) view?.showProgress(false)
}
After a few days, I managed to solve the problem by updating the SDK to version 28. RecyclerView no longer causes animation jams when inflating