In my app I have a MainActivity which has mobile navigation implemented with a navBar and all that stuff. When I navigate to a fragment there needs to be a Youtube Video Player inside. As I'm developing a one activity application so far I tried to implement the Fragment approach on the Youtube API.
I'm having issues with YoutubePlayerSupportFragment. I made it work following this suggestion: https://stackoverflow.com/a/58792809/13150066
But this solution is, to me, kind of shady. I'm afraid this solution will crash sometime, or will not work as the API itself would.
This is the error was having with 'android.support.v4.app.Fragment'
And as the suggestion above suggests... I created a new custom class, YoutubePlayerSupportFragmentX which extends from the Fragment class that I have no issues with, androidx.fragment.app.Fragment, and this is it's code:
YoutubePlayerSupportFragmentX.kt
package com.google.android.youtube.player //<--- IMPORTANT!!!!
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.internal.ab
import java.util.*
class YouTubePlayerSupportFragmentX : Fragment(), YouTubePlayer.Provider {
private val a = ViewBundle()
private var b: Bundle? = null
private var c: YouTubePlayerView? = null
private var d: String? = null
private var e: YouTubePlayer.OnInitializedListener? = null
override fun initialize(var1: String, var2: YouTubePlayer.OnInitializedListener) {
d = ab.a(var1, "Developer key cannot be null or empty")
e = var2
a()
}
private fun a() {
if (c != null && e != null) {
c?.a(this.activity, this, d, e, b)
b = null
e = null
}
}
override fun onCreate(var1: Bundle?) {
super.onCreate(var1)
b = var1?.getBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE")
}
override fun onCreateView(var1: LayoutInflater, var2: ViewGroup?, var3: Bundle?): android.view.View? {
c = YouTubePlayerView(Objects.requireNonNull(this.activity), null, 0, a) // and this line compiles but gives red warning
a()
return c
}
override fun onStart() {
super.onStart()
c?.a()
}
override fun onResume() {
super.onResume()
c?.b()
}
override fun onPause() {
c?.c()
super.onPause()
}
override fun onSaveInstanceState(var1: Bundle) {
super.onSaveInstanceState(var1)
(if (c != null) c?.e() else b)?.let { var2 ->
var1.putBundle("YouTubePlayerSupportFragment.KEY_PLAYER_VIEW_STATE", var2)
}
}
override fun onStop() {
c?.d()
super.onStop()
}
override fun onDestroyView() {
this.activity?.let { c?.c(it.isFinishing) }
c = null
super.onDestroyView()
}
override fun onDestroy() {
if (c != null) {
val var1 = this.activity
c?.b(var1 == null || var1.isFinishing)
}
super.onDestroy()
}
private inner class ViewBundle : YouTubePlayerView.b {
override fun a(var1: YouTubePlayerView, var2: String, var3: YouTubePlayer.OnInitializedListener) {
e?.let { initialize(var2, it) }
}
override fun a(var1: YouTubePlayerView) {}
}
companion object {
fun newInstance(): YouTubePlayerSupportFragmentX {
return YouTubePlayerSupportFragmentX()
}
}
}
And this is my fragment class in which I implement the YoutubePlayerSupportFragmentX
VideoPlayerFragment.kt
package com.vegdev.vegacademy.ui.learning
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.google.android.youtube.player.*
import com.vegdev.vegacademy.R
class VideoPlayerFragment : Fragment() {
private var link: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
arguments?.let {
val safeArgs = VideoPlayerFragmentArgs.fromBundle(it)
link = safeArgs.link
}
val youtubePlayerFragment = YouTubePlayerSupportFragmentX.newInstance()
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.player, youtubePlayerFragment).commit()
youtubePlayerFragment.initialize(resources.getString(R.string.API_KEY), object : YouTubePlayer.OnInitializedListener {
override fun onInitializationSuccess(
p0: YouTubePlayer.Provider?,
p1: YouTubePlayer?,
p2: Boolean
) {
p1?.loadVideo(link)
}
override fun onInitializationFailure(
p0: YouTubePlayer.Provider?,
p1: YouTubeInitializationResult?
) {
}
})
return inflater.inflate(R.layout.fragment_video_player, container, false)
}
}
fragment_video_player.XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/blackBackground"
tools:context=".ui.learning.VideoPlayerFragment">
<FrameLayout
android:id="#+id/player"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I tried changing dependencies, I tried erasing folder "/.idea/libraries", cleans and builds, everything I could find online. The only thing that did it was the suggestion above.
So my questions are:
Why am I getting that error with Fragment v4?
Am I implementing it wrong? Because it works just fine, except for the fullscreen but I've read that it's a common issue.
If you've implemented a Youtube Video inside a fragment, did you use another API? Is this the only one?
Put your class on a fragment directly over an activity such as:
<fragment android:name="com.google.android.youtube.player.YouTubePlayerSupportFragmentX" android:id="#+id/fragPlayer" android:layout_width="match_parent" android:layout_height="match_parent"/>
Your activity may implements YouTubePlayer.OnInitializedListener, and on your onCreate event call to object and initializate it:
val playerView : YouTubePlayerSupportFragmentX = supportFragmentManager.findFragmentById(R.id.fragPlayer) as YouTubePlayerSupportFragmentX
playerView.initialize(getString(R.string.YOUTUBE_API_KEY), this)
Remember to include your class YouTubePlayerSupportFragmentX on the com.google.android.youtube.Player.YouTubePlayerSupportFragmentX package.
In my case, I used only one API. This how it looks like:
Related
Whenever I use the Notification Shade to switch between the device dark and light themes, (my app when running) always seems to crash for some reason. My app's minimum API is 29 (Android 10). The logcat points to a line of code where the reason for the error isn't obvious. (import android.view.*). How can I prevent this from happening again?
java.lang.NullPointerException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkNotNullParameter, parameter view
Activity class
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) supportFragmentManager.beginTransaction()
.replace(R.id.detail_container, MainFragment())
.commitNow()
}
}
Main fragment class
package com.example.vp2
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.app.NavUtils
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import java.util.*
class MainFragment : androidx.fragment.app.Fragment() {
private lateinit var mySpinnerItems: Array<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_main, container, false)
super.onCreate(savedInstanceState)
return v
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
val mSpinner = requireView().findViewById<Spinner>(R.id.mSpinner)
val mViewPager2 = requireView().findViewById<ViewPager2>(R.id.mViewPager2)
// Spinner items array
mySpinnerItems = arrayOf(
"Item 1",
"Item 2",
"Item 3",
)
val arrayAdapter = ArrayAdapter(requireView().context, android.R.layout.simple_spinner_item, mySpinnerItems)
arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_item)
mSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
when {
mySpinnerItems[position] == "Item 1" -> {
mViewPager2.setCurrentItem(0, false)
}
mySpinnerItems[position] == "Item 2" -> {
mViewPager2.setCurrentItem(1, false)
}
else -> {
mViewPager2.setCurrentItem(2, false)
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
mViewPager2.setCurrentItem(0, false)
}
}
mSpinner.adapter = arrayAdapter
mViewPager2.adapter = MySpinnerFragmentAdapter(this)
mViewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
mViewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
when (position) {
0 -> {
mSpinner.setSelection(0)
}
1 -> {
mSpinner.setSelection(1)
}
else -> {
mSpinner.setSelection(2)
}
}
super.onPageSelected(position)
}
})
super.onActivityCreated(savedInstanceState)
}
private class MySpinnerFragmentAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
private val intItems = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> Fragment1()
1 -> Fragment2()
else -> Fragment3()
}
}
override fun getItemCount(): Int {
return intItems
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == android.R.id.home) {
val intent = activity?.let { NavUtils.getParentActivityIntent(it) }
when {
intent != null -> {
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
NavUtils.navigateUpTo(requireActivity(), intent)
}
}
true
} else super.onOptionsItemSelected(item)
}
}
Main activity layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/detail_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
Main fragment layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/detail_container">
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/mViewPager2"
android:layout_height="0dp"
android:layout_width="match_parent"
android:layout_weight="1" />
<!-- divider (start)-->
<View
android:id="#+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="5dp"
android:background="?android:attr/textColorSecondary" />
<!-- divider (end)-->
<Spinner
android:id="#+id/mSpinner"
style="#style/Widget.AppCompat.Spinner.Underlined"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:spinnerMode="dropdown"/>
</LinearLayout>
UPDATE
cactustictacs' suggestion
mSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
when {
mySpinnerItems[position] == "Item 1" -> {
mViewPager2.setCurrentItem(0, false)
}
mySpinnerItems[position] == "Item 2" -> {
mViewPager2.setCurrentItem(1, false)
}
else -> {
mViewPager2.setCurrentItem(2, false)
}
}
}
override fun onNothingSelected(parent: AdapterView<*>) {
// Code to perform some action when nothing is selected
mViewPager2.setCurrentItem(0, false)
}
}
The stacktrace (please post the actual text in future not a screenshot!) is saying some parameter called view is null when its type is specified as non-null (View instead of View?). And that's being caused by AdapterView.onItemSelected running (so it's the view parameter in that method), which is declared in onActivityCreated
Basically that onItemSelected method needs to have nullable types for the first two parameters, AdapterView<*>? and View?.
That's what you get if you let the IDE auto-implement the methods (with ctrl+I) - the docs have that View! type which means because it's coming from Java, it could be null, might not, don't know - so the safe default is View?. When you specify a non-null type (like View) Kotlin does a null check to make sure - that's what that Intrinsics.checkNotNullParameter call is. You got a null, so it threw an exception!
So yeah, make them nullable, then null-check them before you access them. Also make sure appcompat is up to date (at least 1.2.0) because they had an issue where Activitys weren't being recreated when you used setDefaultNightMode
I'm trying to use startActionMode inside a fragment.
startActionMode works fine in the onActivityCreated method. But outside onActivityCreated method, activity returns null and hence activity?.startActionMode(mActionModeCallback) doesn't work.
package com.akshat.music
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_songs.*
import java.io.File
class SongsFragment : Fragment(), SongListAdapter.OnItemClickListener{
private val allSongs = ArrayList<Songs>()
var activityRef: FragmentActivity? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityRef = activity;
if (activityRef == null)
Log.d("MIKE", " $activityRef is empty");
else
Log.d("MIKE", " $activityRef is not empty");
recyclerView.apply {
layoutManager = LinearLayoutManager(activity)
adapter = SongListAdapter(allSongs, SongsFragment().apply { this#SongsFragment })
}
activity?.startActionMode(mActionModeCallback) // works
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
getAudioDirectories()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater!!.inflate(R.layout.fragment_songs, container, false);
}
override fun onItemClicked(song: Songs, position: Int) {
if (activityRef == null)
Log.d("MIKE", " $activityRef is empty");
else
Log.d("MIKE", " $activityRef is not empty");
activity?.startActionMode(mActionModeCallback) // doesn't work
activityRef?.startActionMode(mActionModeCallback) // activityRef is null
}
private val mActionModeCallback: ActionMode.Callback =
object : ActionMode.Callback {
override fun onCreateActionMode(
actionMode: ActionMode,
menu: Menu
): Boolean {
val menuInflater = actionMode.menuInflater
menuInflater.inflate(R.menu.contextual_action_bar, menu)
return true
}
override fun onPrepareActionMode(
actionMode: ActionMode,
menu: Menu
): Boolean {
return false
}
override fun onActionItemClicked(
actionMode: ActionMode,
menuItem: MenuItem
): Boolean {
return false
}
override fun onDestroyActionMode(actionMode: ActionMode) {
var actionMode: ActionMode? = actionMode
actionMode = null
}
}
private fun getAudioDirectories() {
/////////////
}
}
}
How can I get reference to activity outside onActivityCreated method?
Thanks
You should be able to get a reference to the activity from anywhere inside a fragment.
there are some cases where activity can be null e.g inside onDetach() or inside OnCreateView(), so activity doesn't always require a value.
try to use the requireContext() like Stachu has mentioned:
(requireContext() as Activity).startActionMode(mActionModeCallback)
you can cast context as Activity and call it from there, e.g.
override fun onItemClicked(song: Songs, position: Int) {
...
(requireContext() as Activity).startActionMode(mActionModeCallback)
}
got error fragment already added on my MyPagerAdapter class which extends FragmentPagerAdapter
this is my error logs
E/AndroidRuntime: FATAL EXCEPTION: main
Process: id.cahyowhy.tmdbmovieimplementation, PID: 8228
java.lang.IllegalStateException: Fragment already added: MovieFragment{df79747} (01cf33b8-9eb3-4dce-9519-384ca7dd7570) id=0x7f08014e android:switcher:2131231054:0}
at androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:67)
at androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1563)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:405)
at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
this is my pageradapter code
class MyPagerAdapter(fm: FragmentManager) :
FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val pages = listOf(
MovieFragment.newInstance(MovieListType.POPULAR),
MovieFragment.newInstance(MovieListType.TOP_RATED),
MovieFragment.newInstance(MovieListType.UPCOMING)
)
override fun getItem(position: Int): Fragment {
return pages[position]
}
override fun getCount(): Int {
return pages.size
}
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> "Popular"
1 -> "Top Rated"
else -> "Up Coming"
}
}
}
this is my movie fragment code
package id.cahyowhy.tmdbmovieimplementation.ui.activity.fragment
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import id.cahyowhy.tmdbmovieimplementation.databinding.MoviesFragmentResultBinding
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.ItemTypeFactoryImpl
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.VisitableRecyclerAdapter
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.ErrorStateItem
import id.cahyowhy.tmdbmovieimplementation.ui.adapter.viewholder.MovieItem
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseFragment
import id.cahyowhy.tmdbmovieimplementation.ui.base.BaseViewItem
import id.cahyowhy.tmdbmovieimplementation.ui.util.ext.observe
import org.koin.android.viewmodel.ext.android.viewModel
open class MovieFragment(
private val movieListType: MovieListType
) : BaseFragment() {
private val viewModel by viewModel<MovieFragmentViewModel>()
private lateinit var binding: MoviesFragmentResultBinding
private val movieAdapter by lazy {
VisitableRecyclerAdapter(
ItemTypeFactoryImpl(),
::onItemClicked
)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = MoviesFragmentResultBinding.inflate(
inflater,
container,
false
)
return binding.root
}
override fun onResume() {
super.onResume()
viewModel.loadData(0, movieListType)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(binding) {
recyclerView.adapter = movieAdapter
recyclerView.setHasFixedSize(true)
swipeRefresh.setOnRefreshListener { viewModel.loadData(0, movieListType) }
}
super.onViewCreated(view, savedInstanceState)
}
override fun observeChange() {
observe(viewModel.loading, ::handleLoading)
observe(viewModel.movies, ::onDataLoaded)
observe(viewModel.toastMessage, ::showSnackbarMessage)
}
private fun handleLoading(status: Boolean) {
binding.swipeRefresh.isRefreshing = status
}
private fun onDataLoaded(items: List<BaseViewItem>) {
movieAdapter.submitList(items)
}
private fun onItemClicked(viewItem: BaseViewItem, view: View) {
when (viewItem) {
is MovieItem -> {
Log.d("MovieItem", "MovieItem Click: ${viewItem.title}")
}
is ErrorStateItem -> {
viewModel.loadData(0, movieListType)
}
}
}
companion object {
fun newInstance(movieListType: MovieListType): MovieFragment =
MovieFragment(movieListType)
}
}
and this is when i called from MainActivity.kt
class MainActivity : BaseActivity() {
private lateinit var binding: ActivityMainBinding;
....
fun initView() {
with(binding) {
viewPager.adapter = MyPagerAdapter(supportFragmentManager)
viewPagerTab.setupWithViewPager(viewPager)
}
}
can anyone solve this.. thanks..
The problem is MyFragment is a class and the FragmentManager is something that you get by calling supportFragmentManager. This is provided to you by the system. For the fragment manager to be able to add new fragments to the back stack, the Fragments provided to it should be unique.
Now I do not know what your use case is but calling MyFragment.newInstance() does not provide you unique classes as they are just new instances of the same MyFragment class. The error log is stating that.
To get around this you can create three different Fragment classes like PopularMoviesFragment, TopRatedMoviesFragment and UpcomingMoviesFragment and then add them to your list on the adapter class.
I know it seems like a lot of work but this way you have three different fragments that shows data for three different cases of Movies and you can have additional logic in them. And you will also not hit java.lang.IllegalStateException: Fragment already added: MovieFragment error
Your Adapter code where you call:
private val pages = listOf(
MovieFragment.newInstance(MovieListType.POPULAR),
MovieFragment.newInstance(MovieListType.TOP_RATED),
MovieFragment.newInstance(MovieListType.UPCOMING)
)
should now look like:
private val pages = listOf(
PopularMoviesFragment.newInstance(MovieListType.POPULAR),
TopRatedMoviesFragment.newInstance(MovieListType.TOP_RATED),
UpcomingMoviesFragment.newInstance(MovieListType.UPCOMING)
)
I am getting null pointers (sometimes) on views within fragments using synthetic.
I do not know what is wrong because I am declaring the fragments in XML and I think that when I call the populate method from the Activity, the root view is already created
Anyway, I do not believe is correct check this each time I call the populate...
I write an example code of what happens (the null pointer would be on tv):
The fragment:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_foo.*
class FooFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_foo, container, false)
}
fun populate(fooName: String) {
tv.text = fooName
}
}
And the XML related within the XML of the Activity related
<fragment
android:id="#+id/fFoo"
android:name="com.foo.FooFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout="#layout/fragment_foo"/>
This would the Activity related:
class FooActivity : AppCompatActivity() {
private lateinit var fooFragment: FooFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_foo)
fooFragment = fFoo as FooFragment
}
private fun loadDataProductionInfo(productionId: Long) {
FooAPIParser().getFoo {
initFoo(it.fooName)
}
}
private fun initFoo(fooName: String) {
fooFragment.populate(fooName)
}
}
And finally the XML of the specific fragment:
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvFoo"
style="#style/Tv"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Kotlin synthetic properties are not magic and work in a very simple way. When you access btn_K, it calls for getView().findViewById(R.id.tv)
The problem is that you are accessing it too soon getView() returns null in onCreateView. Try doing it in the onViewCreated method:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//Here
}
}
Using views outside onViewCreated.
tv?.text = "kotlin safe call"
If you are going to use Fragments you need to extend FragmentActivity
Thinking in your answers I have implemented a patch. I do not like very much, but I think it should work better than now. What do you think?
I would extend each Fragment I want to check this from this class:
import android.os.Bundle
import android.os.Handler
import android.view.View
import androidx.fragment.app.Fragment
open class BaseFooFragment : Fragment() {
private var viewCreated = false
private var attempt = 0
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
}
fun checkViewCreated(success: () -> Unit) {
if (viewCreated) {
success()
} else {
initLoop(success)
}
}
private fun initLoop(success: () -> Unit) {
attempt++
Handler().postDelayed({
if (viewCreated) {
success()
} else {
if (attempt > 3) {
return#postDelayed
}
checkViewCreated(success)
}
}, 500)
}
}
The call within the Fragment would be more or less clean:
fun populate(fooName: String) {
checkViewCreated {
tv.text = fooName
}
}
Finally, I have received help to find out a better answer.
open class BaseFooFragment : Fragment() {
private var listener: VoidListener? = null
private var viewCreated = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
listener?.let {
it.invoke()
listener = null
}
}
override fun onDestroyView() {
viewCreated = false
super.onDestroyView()
}
fun doWhenViewCreated(success: VoidListener) {
if (viewCreated) {
success()
} else {
listener = success
}
}
}
The VoidListener is simply this:
typealias VoidListener = () -> Unit
One way to do this more generic (for example, when you want to use more than one listener) could be like this:
open class BaseFooFragment : Fragment() {
private val listeners: MutableList<VoidListener> = mutableListOf()
private var viewCreated = false
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewCreated = true
listeners.forEach { it() }
listeners.clear()
}
override fun onDestroyView() {
viewCreated = false
super.onDestroyView()
}
fun doWhenViewCreated(success: VoidListener) {
if (viewCreated) {
success()
} else {
listeners.add(success)
}
}
}
folks. I have been working on a project with kotlin and I need to make a fragment that comunicate with the parent activity... I followed exactly what google and other websites suggested but I still get an error "activity does not override anything"... All of the other solutions are not working for me... here is the code .
FRAGMENT
package com.me.assistan.assistant
import android.app.Activity
import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.content.Context
import android.content.Intent
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.LinearLayout
import android.widget.ToggleButton
import kotlinx.android.synthetic.main.content_newplan.*
import java.util.*
class addingTask : Fragment(), View.OnClickListener{
var func = Functions
var globalTask = GlobalTask
private lateinit var listener: OnTimeSettedListener
override fun onAttach(context: Context?) {
super.onAttach(context)
if (context is OnTimeSettedListener) {
listener = context
} else {
throw ClassCastException(context!!.toString() + " must implement
OnTimeSettedListener.")
}
}
companion object {
fun newInstance(): addingTask {
return addingTask()
}
}
override fun onCreateView(inflater: LayoutInflater?, container:
ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_adding_task,
container,
false)
val activity = activity
view.theTime.setOnClickListener { v ->
listener.onTimeSetListtedener("test")
}
return view
}
interface OnTimeSettedListener{
fun onTimeSetListtedener(comic : String){
println("ok")
}
}
}// Required empty public constructor
And not the MAIN ACTIVITY
class Newplan : AppCompatActivity(), addingTask.OnTimeSettedListener {
var posx = 0f
private var startx = 0f
private var posy = 0f
private var starty = 0f
var backIntent = Intent();
var func = Functions
var globalTask = GlobalTask
val fragment = addingTask()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_newplan)
if(savedInstanceState === null){
var args = Bundle()
supportFragmentManager.beginTransaction()
.add(R.id.taskMain, addingTask.newInstance(),"newTask")
.commit()
}
}
override fun onTimeSettedListener(comic : String){
println("params")
}
}
I get the error on the activity class... When I remove the "override?, the error is gone but nothing happen when I click on the button... What am I doing wrong?
I think you shouldn't add method body to your interface method. It is not allowed in Java. In Kotlin there is no error that showing method body is restricted. But you should remove body. Change your interface like
interface OnTimeSettedListener{
fun onTimeSetListtedener(comic : String)
}
Also actually you are not overriding your Listener's method. Method name in OnTimeSettedListener is onTimeSetListtedener but you are overriding onTimeSettedListener which is not really exist in your code.
Also as #albodelu mentioned answer and #chris mentioned in comments, you cannot write methods in methods. It is not correct usage.
As #chris commented, you need to move the lines below outside of onCreate() method:
override fun onTimeSettedListener(comic: String) {
println("params")
}
You also need to match names replacing
interface OnTimeSettedListener {
fun onTimeSetListtedener(comic : String){
println("ok")
}
}
by
interface OnTimeSettedListener {
fun onTimeSettedListener(comic: String) {
println("ok")
}
}
Update
If you fix the name typo and remove the default implementation of the onTimeSettedListener declaration in the interface inside your fragment, as your activity implements it and Android Studio warns you about the missing override, it's possible to click it and select that the IDE implements it for you to avoid errors doing it:
interface OnTimeSettedListener{
fun onTimeSettedListener(comic : String)
}
You also need to fix the call replacing:
listener.onTimeSetListtedener("test")
by
listener.onTimeSettedListener("test")