Issue
The YouTube documentation does not clearly outline where to call seekToMillis() in the YouTube lifecycle of their API guide or documentation for Android.
In order to make sure the YouTube player picks up from where it left of when the screen is rotated seekToMillis(...) is required. However, this method does not work as expected being called directly before or after loadVideo(...) or play().
Expected
Call seekToMillis(...) to start video at specific point in milliseconds.
Solution
The seekToMillis(...) method works after events within the YouTubePlayer.PlayerStateChangeListener. Therefore, the saved instance state bundle can be passed into the inner class made for the PlayerStateChangeListener.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentYoutubeDialogBinding.inflate(inflater, container, false)
val youTubePlayerFragment = YouTubePlayerSupportFragment.newInstance()
youTubePlayerFragment.initialize(Auth.APP_API_ID, object : OnInitializedListener {
override fun onInitializationSuccess(provider: Provider, player: YouTubePlayer, wasRestored: Boolean) {
if (!wasRestored) {
youtubePlayer = player
player.setPlayerStateChangeListener(MyPlayerStateChangeListener(savedInstanceState))
player.setPlaybackEventListener(MyPlaybackEventListener())
player.loadVideo(arguments!!.getString(ITEM_ID))
player.play()
}
}
override fun onInitializationFailure(provider: Provider, result: YouTubeInitializationResult) {
// TODO: add log statement.
}
})
childFragmentManager.beginTransaction().replace(R.id.youtubePlayer, youTubePlayerFragment as Fragment).commit()
return binding.root
}
private inner class MyPlayerStateChangeListener(var savedInstanceState: Bundle?) : YouTubePlayer.PlayerStateChangeListener {
internal var playerState = "UNINITIALIZED"
override fun onLoading() {
playerState = "LOADING"
}
override fun onLoaded(videoId: String) {
playerState = String.format("LOADED %s", videoId)
}
override fun onAdStarted() {
playerState = "AD_STARTED"
}
override fun onVideoStarted() {
if (savedInstanceState != null) {
println("YT_CURRENT: " + savedInstanceState!!.getInt(YOUTUBE_CURRENT_TIME_KEY))
youtubePlayer.seekToMillis(savedInstanceState!!.getInt(YOUTUBE_CURRENT_TIME_KEY))
playerState = "VIDEO_STARTED"
}
}
Related
MyActivity is...
private var preso: ClientResultPresentationFragment? = null
private var presoHelper: PresentationHelper? = null
private val presoListener = object: PresentationHelper.Listener{
override fun initSecondDisplay(display: Display?) {
Log.d("preso", "initSecondDisplay()")
preso = MytPresentationFragment.newInstance(this#MyActivity, display)
preso!!.show(fragmentManager, PRESO) // PRESO is a static value.
}
override fun clearPreso(switchToInline: Boolean) {
if (preso != null) {
preso!!.dismiss()
preso = null
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// Must open second display at the same time.
onCreatePreso()
refinedResultData = intent.extras!!.getParcelable(RESULT_DATA)
Log.d("result", "${resultData.toString()}")
init(resultData!!)
}
private fun init(data: ResultData){
initView(data)
preso!!.syncData(data)
}
private initView(data: ResultData){
// TODO: initViews...
}
private fun onCreatePreso(){
presoHelper = PresentationHelper(this, presoListener)
presoHelper!!.enable()
}
override fun onResume() {
super.onResume()
presoHelper?.onResume()
}
override fun onPause() {
presoHelper?.onPause()
super.onPause()
}
My Presentation Fragment is...
var mView: View? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d("preso", "onCreateView()")
mView = LayoutInflater.from(context).inflate(R.layout.fragment_my, container, false)
return mView
}
fun syncData(data: ResultData){
Log.d("preso", "syncData()->${data.toString()}")
initView()
// return >>> here...
mView!!.tv_title.text = "${data.title}" // <<< crash here >>>
// TODO: set initial data
}
fun initView(){
// initViews...
}
companion object {
fun newInstance(context: Context?, display: Display?): MyPresentationFragment {
val frag = MyPresentationFragment()
frag.setDisplay(context, display)
return frag
}
}
And the log is...
"resultData.toString()"
initSecondDisplay()
"syncData()->${data.toString()}"
crash------------------
if I just uncomment return part in syncData() of Presentation Fragment(It will not access the views)
"resultData.toString()"
initSecondDisplay()
"syncData()->${data.toString()}"
onCreateView()
So, syncData() is called earlier than onCreateView(). What should I do? I moved preso!!.syncData() after preso!!.show(fragmentManager, PRESO) and it's the same.
Since FragmentManager operations is async, you should send a callback from your fragment to the activity in the fragment OnViewCreated or maybe OnResume and only then send the data from the activity to the fragment.
Or you can just use ViewModel and LiveData to provide your data to fragments
Get next error from Crashlytics for Android version: 10:
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.content.Context.getSystemService(java.lang.String)' on a null object reference
at android.app.Dialog.<init>(Dialog.java:196)
at android.app.ColorBaseAlertDialog.<init>(ColorBaseAlertDialog.java:33)
at android.app.AlertDialog.<init>(AlertDialog.java:208)
at android.app.AlertDialog.<init>(AlertDialog.java:204)
at android.app.ProgressDialog.<init>(ProgressDialog.java:112)
at com.sau.authenticator.widget.fragment.BaseFragment.createProgressDialog(BaseFragment.java:46)
at com.sau.authenticator.widget.fragment.BaseFragment.showLoadProgress(BaseFragment.java:41)
at com.sau.authenticator.widget.fragment.WebViewFragment$webViewClient$1.onPageStarted(WebViewFragment.java:69)
at i6.c(i6.java:2)
at Hn.handleMessage(Hn.java:145)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:228)
at android.app.ActivityThread.main(ActivityThread.java:7782)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:981)
In my function createProgressDialog, which is called in BaseFragment:
abstract class BaseFragment : Fragment() {
private var progressDialog: ProgressDialog? = null
val activityComponents: ActivityComponentsContract?
get() = activity?.activityComponentsContract
override fun onDestroy() {
progressDialog?.dismiss()
progressDialog = null
super.onDestroy()
}
protected fun showLoadProgress() {
if (progressDialog == null) progressDialog = createProgressDialog()
progressDialog?.show()
}
private fun createProgressDialog(): ProgressDialog? {
val dialog = ProgressDialog(activity, R.style.ProgressDialogTheme)
dialog.setCancelable(false)
dialog.setProgressStyle(android.R.style.Widget_ProgressBar_Large)
return dialog
}
protected fun dismissLoadProgress() {
progressDialog?.dismiss()
}
}
Here is code of my WebViewFragment:
class WebViewFragment : BaseFragment() {
private var url = ""
private var title = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
url = it.getString(KEY_URL, "")
title = it.getString(KEY_TITLE, "")
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
activityComponents?.updateAppbar(
title = title,
backActionImageResId = R.drawable.ic_appbar_action_back
)
return inflater.inflate(R.layout.fragment_web_view, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
customWebView?.webViewClient = webViewClient
customWebView?.loadUrl(url)
}
private val webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
showLoadProgress()
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
dismissLoadProgress()
}
}
companion object {
const val KEY_URL = "KEY_URL"
fun newBundle(url: String = "", title: String): Bundle {
return Bundle().apply {
putString(KEY_URL, url)
putString(KEY_TITLE, title)
}
}
}
}
I'm not too familiar with WebView, but it seems like by the time onPageStarted() is called your user already navigated away (or rotated his device). Hence your Fragment's onDestroyView() is likely called already, but you are not notifying your WebView that starts loading your url. That's why activity is null and you get the exception.
So on your WebViewFragment's lifecycle callbacks make sure you notify your WebView too:
override fun onResume() {
super.onResume()
customWebView?.onResume()
}
override fun onPause() {
super.onPause()
customWebView?.onPause()
}
override fun onDestroyView() {
super.onDestroyView()
customWebView?.stopLoading() // this should prevent onPageStarted() from being called
}
It seems that this error happened when your Fragment was DestroyView. I am not entirely sure about this. You can reproduce by load a large web page, then navigate to another fragment so that WebViewFragment / onViewDestroyed is executed. Print logs in onPageStarted and onPageFinished. If you see them being called when you destroy the view, this is exactly the cause. At this point you just need to check the lifecycle state to show or hide progress.
My app has few activities and fragments. One of the activities is radio player. User can control player using a media notification (and a service). And it's working nice. But then I added to the radio activity a fragment with webview (site "chatovod"). And saw when I open this fragment (webview) my media notification is closing (but not service - music still playing). By the way other notifications (from the image activity) still showing.
class ChatovodFragment: Fragment() {
private lateinit var webView: WebView
companion object {
fun newInstance(url: String): ChatovodFragment {
val fragment = ChatovodFragment()
val bundle = Bundle()
bundle.putString(BUNDLE_CHATOVOD_URL, url)
fragment.arguments = bundle
return fragment
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.onBackPressedDispatcher?.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
activity?.supportFragmentManager?.popBackStack()
}
}
})
}
#SuppressLint("SetJavaScriptEnabled")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_chatovod, container, false)
val url: String =
if (arguments != null) {
requireArguments().getString(BUNDLE_CHATOVOD_URL, "")
} else
""
webView = view.findViewById(R.id.webView)
webView.settings.javaScriptEnabled = true
webView.loadUrl(url)
webView.webViewClient = MyWebViewClient()
return view
}
class MyWebViewClient: WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
view?.loadUrl(request?.url.toString())
return true
}
}
The service code is big. So if you need it I could load it to other place.
Sorry for my ignorance of English,but i hope you could understand what i am trying to anwser.
This question happened more than one place in my application.
In my application it is the audio focus make it.
Code:
systemService.abandonAudioFocus(new AudioManager.OnAudioFocusChangeListener() {
#Override
public void onAudioFocusChange(int focusChange) {
}
});
or other override method with it .
Do something in it.
Hope it helps you man.
None of the other instances of this question are solving my problem. I have a Fragment that appears at the end of a transaction sequence. It is meant to close the app when a Timer contained within it completes:
var terminalTimer: Timer? = null
class TerminalFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_terminal, container, false)
}
override fun onStart() {
super.onStart()
initUi()
startCountDown()
}
override fun onStop() {
super.onStop()
AppLog.i(TAG, "onStop()")
stopCountDown()
}
private fun startCountDown() {
if (terminalTimer == null) {
terminalTimer = Timer()
terminalTimer!!.schedule(object : TimerTask() {
override fun run() {
AppLog.i(TAG, "Terminal countdown finished")
{
activity?.finish()
}
}, 5000)
}
}
private fun stopCountDown() {
AppLog.i(TAG, "stopCountDown()")
terminalTimer?.cancel()
terminalTimer?.purge()
terminalTimer = null
}
private fun returnToStart() {
AppLog.i(TAG, "returnToStart()")
(context as MainActivity).restartFlow() // calls popBackStackImmediate() for every fragment in the backstack, returning user to the beginning of their flow
}
companion object {
#JvmStatic
fun newInstance(terminalType: String, amountLoaded: Double) =
TerminalFragment().apply {
arguments = Bundle().apply {
}
}
}
}
stopCountDown() is being called whenever the fragment is navigated away from, but it somehow survives sometimes and closes the app from another Fragment. Using logs, I've also discovered that there appears to be 2 instances of this timer sometimes. How do I insure that this countdown is never active outside of this fragment and is cancelled/ destroyed in the Fragment's onStop()?
I am using Kotlin Android Extension to access view directly by their id.
I have a progress bar which I access directly in fragment using id i.e progress_bar
<ProgressBar
android:id="#+id/progress_bar"
style="#style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="15dp"
android:indeterminate="true"/>
In fragment, I am showing and hiding it with this code
progress_bar.visibility = if (visible) View.VISIBLE else View.GONE
It is working perfectly until I rotate the screen. After that, it throws the exception
java.lang.IllegalStateException: progress_bar must not be null.
The variable gets null on screen rotation. How to solve this problem?
Fragment code
class SingleAppFragment : Fragment() {
private lateinit var appName: String
companion object {
fun newInstance(appName: String = ""): SingleAppFragment {
val fragment = SingleAppFragment()
val args = Bundle()
args.putString(Constants.EXTRA_APP_NAME, appName)
fragment.arguments = args
return fragment
}
}
private var mListener: OnFragmentInteractionListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appName = if (arguments != null && !arguments.getString(Constants.EXTRA_APP_NAME).isEmpty()) {
arguments.getString(Constants.EXTRA_APP_NAME)
} else {
Constants.APP_NAME_FACEBOOK
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.fragment_single_app, container, false)
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
setEventListeners()
}
private fun initView() {
var canShowSnackBar = true
web_single_app.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
showHideProgressBar(true)
canShowSnackBar = true
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
showHideProgressBar(false)
}
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
web_single_app.stopLoading()
if (canShowSnackBar) {
mListener?.onErrorWebView()
canShowSnackBar = false
}
}
}
web_single_app.settings.javaScriptEnabled = true
web_single_app.loadUrl(Constants.APP_NAME_URL_MAP[appName])
}
private fun setEventListeners() {
back_web_control.setOnClickListener({
web_single_app.goBack()
})
}
fun showHideProgressBar(visible: Boolean) {
progress_bar_web_control.visibility = if (visible) View.VISIBLE else View.GONE
}
fun loadUrl(appName: String) {
web_single_app.loadUrl(Constants.APP_NAME_URL_MAP[appName])
}
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is OnFragmentInteractionListener) {
mListener = context
}
}
override fun onDetach() {
super.onDetach()
mListener = null
}
interface OnFragmentInteractionListener {
fun onErrorWebView()
}
}
Steps to reproduce:
Start Activity
Fragment get loaded
At Fragment load, I load an URL and show a progress bar
At loading the URL I rotate the phone and the progress bar variable gets null
In my case this bug happens from time to time. Of course, onViewCreated() is a good method to place your code in. But sometimes it's strangely not enough. And setRetainInstance(true) may help, may not. So sometimes this helps: access your Views with a view variable. You can even access them inside onCreateView(). You can use ?. for a guarantee that an application won't crash (of course, some views won't update in this case). If you wish to get context, use view.context.
In my case this bug reproduced only in Kotlin coroutines.
private fun showProgress(view: View) {
view.progressBar?.visibility = View.VISIBLE
}
private fun hideProgress(view: View) {
view.progressBar?.visibility = View.GONE
}
Then in code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
showData(view)
}
private fun showData(view: View) {
showProgress(view)
adapter = SomeAdapter()
adapter.setItems(items)
val divider = SomeItemDecoration(view.context)
view.recycler_view?.run {
addItemDecoration(divider)
adapter = this#SomeFragment.adapter
layoutManager = LinearLayoutManager(view.context)
setHasFixedSize(true)
}
hideProgress(view)
}
In which method do you get the progress_bar by Id?
Please consider the fragment state lifecycle. Maybe you try to load it when the view is not ready yet.
Ensure your progress_bar variable is assigned only after the view is ready. For example in the
onViewCreated method.
See here the official Android lifecycle:
Update
As #CoolMind pointed out the diagram doesn't show the method onViewCreated.
The complete Android Activity/Fragment lifecycle can be found here:
Add retain intance true to the fragment so that it will not be destroyed when an orientation changes occurs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance=true
}
Also do a null check using safe call operator before accessing views
fun showHideProgressBar(visible: Boolean) {
progress_bar_web_control?.visibility = if (visible) View.VISIBLE else View.GONE
}