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
Related
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
I've been trying to figure this out for 2 days now - I just can't seem to get it to work!
I'm using MVVM with a Repository pattern.
Could someone tell me what I'm doing wrong here?
I'm trying to filter the list to show characters who appeared in specific seasons
e.g any characters from season 2 would be displayed but characters who weren't in season 2 would be omitted from being displayed.
The season list is also from the api endpoint which is why I'm trying to filter it in the viewmodel.
Is this the right way to do this or is there a better/different way to do it ?
Here's my Fragment class
#AndroidEntryPoint
class CharactersFragment : Fragment(R.layout.fragment_character_list) {
private lateinit var binding: FragmentCharacterListBinding
private val recyclerViewAdapter = MyCharactersRecyclerViewAdapter()
private val viewModel: CharactersViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCharacterListBinding.inflate(inflater, container, false)
setHasOptionsMenu(true)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showError()
setupRecyclerView()
navigateToDetails()
}
override fun onOptionsItemSelected(item: MenuItem) =
when (item.itemId) {
R.id.menu_filter -> {
showFilteringPopUpMenu()
true
}
else -> {
false
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.characters_fragment_menu, menu)
val searchItem: MenuItem = menu.findItem(R.id.menu_item_search)
val searchView = searchItem.actionView as SearchView
searchView.apply {
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(queryText: String): Boolean {
Log.d("MainActivity", "QueryTextSubmit: $queryText")
return false
}
override fun onQueryTextChange(queryText: String): Boolean {
Log.d("MainActivity", "QueryTextChange: $queryText")
recyclerViewAdapter.filter.filter(queryText)
return true
}
})
}
}
private fun navigateToDetails() {
recyclerViewAdapter.setOnItemClickListener {
val action =
CharactersFragmentDirections.actionCharactersFragmentToCharacterDetailsFragment(it)
findNavController().navigate(action)
}
}
private fun setupRecyclerView() {
binding.list.apply {
layoutManager =
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = recyclerViewAdapter
viewModel.characters.observe(viewLifecycleOwner) {
it?.let {
recyclerViewAdapter.updateList(it)
Log.d("TAG", "onViewCreated: ${it.size}")
}
}
}
}
private fun showError() {
viewModel.spinner.observe(viewLifecycleOwner) { value ->
value.let { show ->
binding.spinner.visibility = if (show) View.VISIBLE else View.GONE
}
}
viewModel.errorText.observe(viewLifecycleOwner) { text ->
text?.let {
binding.errorTextView.apply {
this.text = text
visibility = View.VISIBLE
}
binding.list.visibility = View.GONE
viewModel.onErrorTextShown()
}
}
}
private fun showFilteringPopUpMenu() {
val view = activity?.findViewById<View>(R.id.menu_filter) ?: return
PopupMenu(requireContext(), view).run {
menuInflater.inflate(R.menu.filter_seasons, menu)
setOnMenuItemClickListener {
when (it.itemId) {
R.id.one -> {
viewModel.season.value = FilterSeasons.SEASON_ONE
Toast.makeText(requireContext(), "Season One", Toast.LENGTH_SHORT)
.show()
}
R.id.two -> {
viewModel.season.value = FilterSeasons.SEASON_TWO
Toast.makeText(requireContext(), "Season Two", Toast.LENGTH_SHORT)
.show()
}
R.id.three -> {
viewModel.season.value = FilterSeasons.SEASON_THREE
Toast.makeText(requireContext(), "Season Three", Toast.LENGTH_SHORT)
.show()
}
R.id.four -> {
viewModel.season.value = FilterSeasons.SEASON_FOUR
Toast.makeText(requireContext(), "Season Four", Toast.LENGTH_SHORT)
.show()
}
R.id.five -> {
viewModel.season.value = FilterSeasons.SEASON_FIVE
Toast.makeText(requireContext(), "Season Five", Toast.LENGTH_SHORT)
.show()
}
else -> {
viewModel.season.value = FilterSeasons.ALL_SEASONS
Toast.makeText(requireContext(), "All Seasons", Toast.LENGTH_SHORT)
.show()
}
}
true
}
show()
}
}
}
And here's my ViewModel
#HiltViewModel
class CharactersViewModel #Inject constructor(
private val repository: Repository
) : ViewModel() {
private val _spinner = MutableLiveData<Boolean>(false)
val spinner: LiveData<Boolean> = _spinner
val season = MutableLiveData<FilterSeasons>()
private val _errorText = MutableLiveData<String?>()
val errorText: LiveData<String?> = _errorText
private val _characters = MutableLiveData<List<BreakingBadCharacterItem>?>()
val characters: LiveData<List<BreakingBadCharacterItem>?> =
_characters.map { seasons ->
when (season.value) {
ALL_SEASONS -> {
seasons
}
SEASON_ONE -> seasons?.filter {
it.appearance.any { it == 1 }
}
SEASON_TWO -> seasons?.filter {
it.appearance.any { it == 2 }
}
SEASON_THREE -> seasons?.filter {
it.appearance.any { it == 3 }
}
SEASON_FOUR -> seasons?.filter {
it.appearance.any { it == 4 }
}
SEASON_FIVE -> seasons?.filter {
it.appearance.any { it == 5 }
}
else -> {
seasons
}
}
}
init {
getAllCharacters()
season.value = ALL_SEASONS
}
private fun getAllCharacters() =
viewModelScope.launch {
try {
_spinner.postValue(true)
val response = repository.loadBBCharacters()
_characters.postValue(response)
} catch (error: BreakingError) {
_errorText.postValue(error.message)
} finally {
_spinner.postValue(false)
}
}
fun onErrorTextShown() {
_errorText.value = null
}
}
I also have an Enum class
enum class FilterSeasons {
ALL_SEASONS,
SEASON_ONE,
SEASON_TWO,
SEASON_THREE,
SEASON_FOUR,
SEASON_FIVE
}
In your menuItemListener you are modifying the viewmodel.season bit you're not actually listening/observing to any changes to this value.
I would recommend a custom setter here (not sure it needs to be liveData as it's not being observed) :
val season: FilterSeasons = FilterSeasons.All
set(value) {
field = value
filterCharacterBySeason(value)
}
And then abstract the filtering you are doing in the character LiveData into itls own method filterCharacterBySeason(season: FilterSeason)
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
I'm trying to get my location using mapbox on an android application built with kotlin. I'm using the locationComponent method to get it, here is my code :
class PlaceholderFragment : Fragment(), OnMapReadyCallback, PermissionsListener {
private var permissionsManager: PermissionsManager = PermissionsManager(this)
private var mapboxMap: MapboxMap? = null
private var myCurrentLocation: LatLng? = null
var navigationMapRoute: NavigationMapRoute? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageViewModel = ViewModelProviders.of(this).get(PageViewModel::class.java).apply {
setIndex(arguments?.getInt(ARG_SECTION_NUMBER) ?: 1)
}
val theActivity = activity as NavigationActivity?
theSteps = theActivity?.theSteps
case = theActivity?.case
case = theActivity.case
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
activity?.let {
Mapbox.getInstance(
it,
getString(com.innoventiq.arkbeh.R.string.mapbox_access_token2)
)
}
val root =
inflater.inflate(com.innoventiq.arkbeh.R.layout.fragment_navigation, container, false)
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
getMyLocation()
}
override fun onExplanationNeeded(permissionsToExplain: MutableList<String>?) {
Toast.makeText(
activity,
com.innoventiq.arkbeh.R.string.user_location_permission_explanation,
Toast.LENGTH_LONG
)
.show()
}
override fun onPermissionResult(granted: Boolean) {
if (granted) {
enableLocationComponent(mapboxMap?.style!!)
} else {
Toast.makeText(
activity,
com.innoventiq.arkbeh.R.string.user_location_permission_not_granted,
Toast.LENGTH_LONG
)
.show()
}
}
override fun onMapReady(mapboxMap: MapboxMap) {
this.mapboxMap = mapboxMap
mapboxMap.cameraPosition = CameraPosition.Builder()
.target(myCurrentLocation)
.zoom(14.0)
.build()
//mapView.setOnTouchListener { v, event -> true }
mapboxMap.setStyle(Style.OUTDOORS) {
enableLocationComponent(it)
}
}
#SuppressLint("MissingPermission")
private fun enableLocationComponent(loadedMapStyle: Style) {
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(activity)) {
// Create and customize the LocationComponent's options
val customLocationComponentOptions = activity?.let {
LocationComponentOptions.builder(it)
.trackingGesturesManagement(true)
.accuracyColor(
ContextCompat.getColor(
activity!!,
com.innoventiq.arkbeh.R.color.colorGreen
)
)
.build()
}
val locationComponentActivationOptions =
activity?.let {
LocationComponentActivationOptions.builder(it, loadedMapStyle)
.locationComponentOptions(customLocationComponentOptions)
.build()
}
// Get an instance of the LocationComponent and then adjust its settings
mapboxMap?.locationComponent?.apply {
// Activate the LocationComponent with options
locationComponentActivationOptions?.let {
this?.activateLocationComponent(
it
)
}
// Enable to make the LocationComponent visible
isLocationComponentEnabled = true
// Set the LocationComponent's camera mode
cameraMode = CameraMode.TRACKING
// Set the LocationComponent's render mode
renderMode = RenderMode.COMPASS
}
} else {
permissionsManager = PermissionsManager(this)
permissionsManager.requestLocationPermissions(activity)
}
}
private fun getMyLocation() {
myCurrentLocation =
mapboxMap?.locationComponent?.lastKnownLocation?.latitude?.let {
mapboxMap!!.locationComponent.lastKnownLocation?.longitude?.let { it1 ->
LatLng(it,
it1
)
}
}
println("the location : $myCurrentLocation ")
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
companion object {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private const val ARG_SECTION_NUMBER = "section_number"
/**
* Returns a new instance of this fragment for the given section
* number.
*/
#JvmStatic
fun newInstance(sectionNumber: Int): PlaceholderFragment {
return PlaceholderFragment().apply {
arguments = Bundle().apply {
putInt(ARG_SECTION_NUMBER, sectionNumber)
}
}
}
}
}
I used almost the full fragment code just trying to give you a clear view of the steps I've used to get the location. When it comes to the line println("the location : $myCurrentLocation ")
inside the getMyLocation() function , it returns this output the location : null ,, any help on this ?
Note
When the map loads, it shows my location perfectly and tracks it, but I just can't get the LatLng of it.
I've found the answer, used LocationComponent.LocationEngine.getLastLocation callBack to get it , here is the code:
private fun getMyLocation() {
mapboxMap?.locationComponent?.locationEngine?.getLastLocation(object :
LocationEngineCallback<LocationEngineResult> {
override fun onSuccess(result: LocationEngineResult?) {
if (result != null) {
myCurrentLocation =
LatLng(result.locations[0].latitude, result.locations[0].longitude)
println("my location is : $myCurrentLocation")
getTheRoute(myCurrentLocation)
}
}
override fun onFailure(exception: Exception) {
toast(getString(R.string.failed_get_location))
}
})
}
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