I have the following fragment handling a ViewPager2 which creates fragments (VideoFragment) on which a video is shown via ExoPlayer:
private const val IMMERSIVE_FLAG_TIMEOUT = 500L
class VideoGalleryFragment : Fragment() {
private lateinit var binding: FragmentVideoGalleryBinding
private lateinit var mediaList: MutableList<File>
private lateinit var mediaViewPager: ViewPager2
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private val videoGalleryFragmentViewModel by viewModels<VideoGalleryFragmentViewModel> { viewModelFactory }
private val args by navArgs<VideoGalleryFragmentArgs>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Mark this as a retain fragment, so the lifecycle does not get restarted on config change
retainInstance = true
// Get root directory of media
//outputDirectory = getOutputDirectory(requireContext())
val rootDirectory = File(args.rootDirectory)
// Walk through all files in the root directory
// We reverse the order of the list to present the last photos first
mediaList = rootDirectory.listFiles { file ->
VIDEO_EXTENSION_WHITELIST.contains(file.extension.toUpperCase(Locale.ROOT))
}?.sortedDescending()?.toMutableList() ?: mutableListOf()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_video_gallery, container, false)
binding.apply {
lifecycleOwner = viewLifecycleOwner
viewModel = videoGalleryFragmentViewModel
}
return binding.root
}
// HERE IS MY VIEWPAGER2 SETUP
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mediaViewPager = binding.videoViewPager.apply {
offscreenPageLimit = 2
adapter = object: FragmentStateAdapter(this#VideoGalleryFragment) {
override fun getItemCount(): Int = mediaList.size
override fun createFragment(position: Int): Fragment =
VideoFragment.create(
mediaList[position]
)
}
setPageTransformer(DepthPageTransformer())
}
}
}
override fun onResume() {
super.onResume()
/*
* Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
* be trying to set app to immersive mode before it's ready and the flags do not stick
* */
binding.container.postDelayed({
binding.container.systemUiVisibility =
FLAGS_FULLSCREEN
}, IMMERSIVE_FLAG_TIMEOUT)
}
}
And here is the Fragment class (used by the ViewPager2) showing the videos via ExoPlayer2:
class VideoFragment : Fragment() {
private var _binding: FragmentVideoBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private var player: SimpleExoPlayer? = null
private var playWhenReady: Boolean? = true
private var currentWindow: Int? = 0
private var playbackPosition : Long? = 0
private var resource: String? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentVideoBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val args = arguments ?: return
args.getString(VideoFragment.VIDEO_FILE_NAME_KEY)?.let {
resource = it
}
}
override fun onDestroyView() {
super.onDestroyView()
releasePlayer()
_binding = null
}
override fun onStart() {
super.onStart()
if(Util.SDK_INT >= 24){
initializePlayer(resource)
}
}
override fun onResume() {
super.onResume()
if((Util.SDK_INT < 24 || player == null)){
initializePlayer(resource)
}
}
private fun initializePlayer(videoFilePath: String?){
player = ExoPlayerFactory.newSimpleInstance(activity)
binding.videoViewGallery.player = player
videoFilePath?.let {
val mediaSource: MediaSource =
ProgressiveMediaSource.Factory(DefaultDataSourceFactory(activity, "exoplayer-mylim"))
.createMediaSource(Uri.parse(it))
playWhenReady?.let { playWhenReady ->
(player as SimpleExoPlayer).playWhenReady = playWhenReady
}
currentWindow?.let { currentWindow ->
playbackPosition?.let {playbackPosition ->
(player as SimpleExoPlayer).seekTo(currentWindow, playbackPosition)
}
}
(player as SimpleExoPlayer).prepare(mediaSource, false, false)
}
}
private fun releasePlayer(){
player?.stop()
player?.release()
player = null
}
override fun onPause() {
super.onPause()
if(Util.SDK_INT < 24){
releasePlayer()
}
}
override fun onStop() {
super.onStop()
if(Util.SDK_INT >= 24){
releasePlayer()
}
}
companion object {
private const val VIDEO_FILE_NAME_KEY = "video_file_name"
fun create(video: File) = VideoFragment().apply {
arguments = Bundle().apply {
putString(VIDEO_FILE_NAME_KEY, video.absolutePath)
}
}
}
}
My problem is that when I switch between the VideoFragments by swiping, the previous Exoplayer is not released properly so that I can still hear the audio of the first video after starting a 2nd video.
Although I have put a releasePlayer() method for releasing the player into the appropriate lifecycle methods, it seems to me that ViewPager does not care about that.
How can I stop/release an ExoPlayer instance when used with ViewPager?
With ViewPager2, your Fragment can be pre-loaded before it is actually displayed to the user. One way to solve your problem is to listen for page changes and releasePlayer() from there, rather than waiting for the Fragment to be destroyed. See the documentation for ViewPager2.OnPageChangeCallback here
I know it is late But Might help someone, I also ran into the same issue. The solution lies in using StatePagerAdapter.
class ViewPagerAdapter extends FragmentStatePagerAdapter {
public ViewPagerAdapter(FragmentManager manager) {
super(manager, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment) {
mFragmentList.add(fragment);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Old That I was Using
class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment) {
mFragmentList.add(fragment);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
The Difference is on Parent Classes
FragmentStatePagerAdapter <== use this Now
class ViewPagerAdapter extends FragmentStatePagerAdapter {
public ViewPagerAdapter(FragmentManager manager) {
super(manager, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
}
FragmentPagerAdapter <== I was using
class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
FragmentPagerAdapter this provides a method which is now deprecated can be seen below:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser){
if (!loaded){
loadStatus();
}
}
}
this was used to manage resources/play/pause based on if a fragment is visible to the user in the View Pager. Deprecated
Now If you want to handle resources/state of the fragment you should use State Pager Adapter as I have described above. Implementing StatePagerAdapter calls onPause() and onResume() methods of a fragment Based on Its Visibility to User.
So answer to the question is Implement Adapter Like StatePagerAdapter and pause the player on Fragment's onPause() method and Resume it in the onResume() method.
Related
I am implementing a function which is to close the application by clicking back button twice. It shows the toast message when it is first clicked. However, the problem is it shows infinite loop error then is termiated. I do not really understand the reason after two days of browsing. Any help will be greatly appreciated.
My fragment code
class RegisteredMainFragment : Fragment() {
private lateinit var binding : FragmentRegisteredMainBinding
private val viewModel : RegisteredMainViewModel by inject()
private lateinit var mContext: MainActivity
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context as MainActivity
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_registered_main, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
onBackPressedCallback()
}
private fun onBackPressedCallback() {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner,
object : OnBackPressedCallback(true ) {
override fun handleOnBackPressed() {
if (doubleBackToExitPressedOnce) {
requireFragmentManager().popBackStack()
return
}
doubleBackToExitPressedOnce = true
Toast.makeText(requireContext(), "click back button again", Toast.LENGTH_SHORT).show()
Handler().postDelayed({ doubleBackToExitPressedOnce = false }, 2000)
}
})
}
override fun onResume() {
super.onResume()
viewModel.onAuthExist()
}
override fun onDestroyView() {
super.onDestroyView()
PushObserverService.unregisterObserver(this)
}
}
I solved the problem by writing it in override fun onAttach
Please check the code below
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context as MainActivity
callback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (doubleBackToExitPressedOnce) {
activity?.finish()
}
doubleBackToExitPressedOnce = true
Handler().postDelayed({ doubleBackToExitPressedOnce = false }, 2000)
showSnackBar(
context = mContext,
layout = binding.layoutMain,
type = Constants.SnackBarTypes.Warn,
message = mContext.getString(R.string.tap_twice_to_terminate)
)
}
}
requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}
I have framelayout in activity and two fragments that I need to attach to the activity depending on the user's choise. When I attach one fragment and change orientation, my fragment gest destrotroyed with activity. How to keep the state of fragment inside activity?
This is my menuActivity which has framelayout.
class MenuActivity : AppCompatActivity(), OptionsFragment.PassFragment {
private val TAG = "Menu"
private lateinit var binding: ActivityMenuBinding
private lateinit var informationViewModel: InformationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMenuBinding.inflate(layoutInflater)
setContentView(binding.root)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
Log.i(TAG, "onCreate: code: ${informationViewModel.hashCode()}")
if (informationViewModel.fragment != null) {
showFragment(informationViewModel.fragment!!)
} else {
showFragment(OptionsFragment())
}
}
private fun showFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(R.id.container, fragment)
.commit()
}
override fun onPassFragment(fragment: Fragment) {
informationViewModel.fragment = fragment
}
}
This is my first fragment which should appear first in activity and it has two textviews for two fragments.
class OptionsFragment : Fragment(R.layout.options_fragment) {
private lateinit var txtPersonal: TextView
private lateinit var txtSecond: TextView
private lateinit var informationViewModel: InformationViewModel
private lateinit var passFragment: PassFragment
private val TAG = "OptionsFragment"
interface PassFragment {
fun onPassFragment(fragment: Fragment)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
txtPersonal = view.findViewById(R.id.person_det)
txtSecond = view.findViewById(R.id.trading_currency)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
Log.i(TAG, "onViewCreated: code: ${informationViewModel.hashCode()} ")
txtPersonal.setOnClickListener {
passFragment(PersonalInfFragment())
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.container, PersonalInfFragment()).commit()
}
txtSecond.setOnClickListener {
passFragment(TradingFragment())
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.container, TradingFragment()).commit()
}
}
private fun passFragment(fragment: Fragment) {
try {
passFragment = activity as PassFragment
passFragment.onPassFragment(fragment)
} catch (ex: ClassCastException) {
}
}
}
Let's say from above options fragment, I opened my second TradingFragment and showed some information from retrofit using the same viewmodel.
class TradingFragment : Fragment(R.layout.currency_trading) {
private val TAG = "TradingFragment"
private lateinit var informationViewModel: InformationViewModel
private lateinit var rec_view: RecyclerView
private lateinit var btnSearch: Button
private lateinit var adapter: TradingAdapter
private lateinit var rangeSeek: RangeSlider
private lateinit var txtStartDate: TextView
private lateinit var txtFinishData: TextView
private lateinit var spinner: Spinner
private var from = 0
private var to = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnSearch = view.findViewById(R.id.btnSearch)
rec_view = view.findViewById(R.id.rec_view)
rangeSeek = view.findViewById(R.id.rangeSlider)
txtStartDate = view.findViewById(R.id.txtFrom)
txtFinishData = view.findViewById(R.id.txtTo)
spinner = view.findViewById(R.id.spinner)
informationViewModel = ViewModelProvider(this).get(InformationViewModel::class.java)
val cur_time = "${System.currentTimeMillis()}".substring(0, 10)
rangeSeek.valueTo = Integer.valueOf(cur_time) * 1.0f
initRecyclerView()
rangeSeek.addOnSliderTouchListener(object : RangeSlider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: RangeSlider) {
}
override fun onStopTrackingTouch(slider: RangeSlider) {
changeDateValueForText(slider.values.get(0).toInt(), slider.values.get(1).toInt())
}
})
btnSearch.setOnClickListener {
startSearchingCurrencyTrading()
}
}
private fun startSearchingCurrencyTrading() {
val pair = spinner.selectedItem.toString()
informationViewModel.startRequestingCurrency(pair, from, to)
}
private fun changeDateValueForText(start: Int, finish: Int) {
from = start
to = finish
val startDate = Date(start * 1000L)
val finishDate = Date(finish * 1000L)
val df = SimpleDateFormat("dd:MM:yyyy")
txtStartDate.text = df.format(startDate)
txtFinishData.text = df.format(finishDate)
}
private fun initRecyclerView() {
adapter = TradingAdapter()
rec_view.adapter = adapter
rec_view.layoutManager = LinearLayoutManager(requireActivity())
informationViewModel.returnCurrentTrading().observe(this) {
if (it != null) {
adapter.updateTradingItems(it)
} else {
adapter.updateTradingItems(listOf())
}
}
}
}
This is my current solution it worked, but I lost my previous query result to retrofit that was being showed on the tradingfragment before orientation change.
Rotation of the screen is defined a configuration change and If you want to preserve the state of your UI I suggest you to implent a ViewModel as described here
However, you have several options to preserve the state of the UI, the choose depends on your needs, all possible scenarios are well explained in the official documentation.
I want develop one application with Rxjava, Dagger, Kotin and MVP.
I write below codes but when run application show me nullPointerExecetpion error.
I know nullPointerExeception for my codes bug, but i try to found my bug i can't it!
My base fragment codes:
abstract class BaseFragment : Fragment(), BaseView {
var presenter: BasePresenter<*>? = null
abstract fun initializeDagger()
abstract fun initializePresenter()
abstract var layoutID: Int
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(layoutID, container, false)
initializeDagger()
initializePresenter()
return view
}
override fun onDetach() {
super.onDetach()
presenter?.onDestroy()
}
}
HomePresenter code:
class HomeTodayPresenter #Inject constructor(
val repositoryUseCase: RepositoryUseCase, disposable: CompositeDisposable
) : BasePresenter<HomeTodayView>(disposable) {
private var todayList = ArrayList<Today>()
fun onCreate() {
view?.initRepositoryList(todayList)
getTodayList()
}
fun onTodayRefreshList() {
getTodayList()
}
private fun getTodayList() {
compositeDisposable.add(
repositoryUseCase.getAuctionsToday()
.subscribe({ responseResult ->
view?.hideLoader()
responseResult?.let { itResponse ->
itResponse.res?.let { itRes ->
itRes.today?.let { itToday ->
if (itToday.size > 0) {
todayList.clear()
todayList.addAll(itToday)
view?.loadRepositoryList()
}
}
}
}
}, { e ->
view?.let { itView ->
e.message?.let { itErr ->
itView.showErrorMessage(itErr)
}
itView.hideLoader()
}
})
)
}
}
Home Fragment code:
class HomeTodayFragment : BaseFragment(), HomeTodayView {
#Inject
lateinit var homeTodayPresenter: HomeTodayPresenter
lateinit var todayAuctionsAdapter: TodayAuctionsAdapter
lateinit var layoutManager: LinearLayoutManager
private val swipeRefreshListener = SwipeRefreshLayout.OnRefreshListener {
homeTodayPresenter.onTodayRefreshList()
}
override fun initRepositoryList(list: ArrayList<Today>) {
layoutManager = LinearLayoutManager(requireContext())
todayAuctionsAdapter = TodayAuctionsAdapter(list)
requireContext().initRecyclerView(homeFragToday_list, layoutManager, todayAuctionsAdapter)
}
override fun loadRepositoryList() {
todayAuctionsAdapter.notifyDataSetChanged()
}
override fun hideLoader() {
homeFragToday_loader.visibility = View.GONE
}
override fun showErrorMessage(msg: String) {
Log.e("responseErr", msg)
}
override fun initializeDagger() {
AndroidInjection.inject(requireActivity())
}
override fun initializePresenter() {
super.presenter = homeTodayPresenter
homeTodayPresenter.view = this
}
override var layoutID: Int = R.layout.fragment_home_today
private lateinit var toolbarTile: TextView
lateinit var handler: Handler
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
homeTodayPresenter.onCreate()
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Initialize
activity?.let {
toolbarTile = it.findViewById(R.id.homePage_toolbarTitle)
}
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
//Initialize
handler = Handler()
//Set delay
handler.postDelayed({
//Set title
toolbarTile.text = resources.getString(R.string.today)
toolbarTile.setTextColor(ResourcesCompat.getColor(resources, R.color.green_active, null))
}, 10)
}
}
}
LogCat error :
kotlin.UninitializedPropertyAccessException: lateinit property homeTodayPresenter has not been initialized
at com.app.applisttestapp.UI.Home.Fragments.Today.HomeTodayFragment.onCreateView(HomeTodayFragment.kt:69)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2539)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:875)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1227)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1293)
Show me error for this line : homeTodayPresenter.onCreate()
How can i fix it?
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
I have OnClickInterface (with method fun onClickShape()) Main.class, and FlipFragment.class and ImageView (which called image in my code). My goal is make listener for image.
interface OnClickInterface {
fun onClickShape()
}
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initTabs()
var flip = FlipFragment()
flip.listener = object : OnClickInterface {
override fun onClickShape() {
Log.d("MainActivity", "Shape Pressed")
ToastUtils.showSuccessMessage(baseContext, "sometext")
}
}
}
fun initTabs() {
var adapter = TabsPagerFragmentAdapter(supportFragmentManager)
mViewPager.adapter = adapter
mTabLayout.setupWithViewPager(mViewPager)
}
}
onCreate in FlipFragment
var image = view.findViewById<ImageView>(R.id.fShapeView)
image.setOnClickListener(View.OnClickListener {
Log.d("FlipFragment", "PRESSED")
if (listener != null)
listener!!.onClickShape()
})
App was loading well, without errors. But when I pressed in the image I show in my log FlipFragment: PRESSED that's mean that my application call method from FragmentFlip, not override method from MainActivity. Why?
I searched error . My app show NPE here.
flip.listener = object : OnClickInterface {
override fun onClickShape() {
Log.d("MainActivity", "Shape Pressed")
ToastUtils.showSuccessMessage(baseContext, "someText")
}}
Why listener = null . I defined it with anonymous class.
All code in FlipFragment
class FlipFragment : Fragment() {
private var layout = R.layout.view_flip
var listener: OnClickInterface? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
var view: View
view = inflater!!.inflate(layout, container, false)
var image = view.findViewById<ImageView>(R.id.fShapeView)
image.setOnClickListener(View.OnClickListener {
Log.d("FlipFragment", "PRESSED")
if (listener != null){
listener!!.onClickShape()}
})
return view
}
companion object {
fun getInstanse(): FlipFragment {
var args = Bundle()
var flipFragment = FlipFragment()
flipFragment.arguments = args
return flipFragment
}
}
}
If you need all code it is FragmentPagerAdapter.class
class TabsPagerFragmentAdapter(fm: FragmentManager?) : FragmentPagerAdapter(fm) {
var tabs: Array<String> = arrayOf("Flip", "Multi")
override fun getItem(position: Int) = when(position){
0 -> FlipFragment.getInstanse()
1 -> Mulit.getInstanse() //it is empty now
else -> FlipFragment.getInstanse()
}
override fun getPageTitle(position: Int) = tabs[position]
override fun getCount() = tabs.size
}