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
Related
I'm trying to inflate a custom dialog in my "CreateShoppingListMenuFragment" I've followed android's documentation but seem to be having a problem with the Listener, I know FragmentManager() is deprecated and used both parentFragmentManager & child FragmentManager to no success, maybe it's related?
Here is the error message:
logo1200.shoppinglist, PID: 24194
java.lang.ClassCastException: com.camilogo1200.shoppinglist.presentation.MainActivity#11852bbmust implement ShoppingListNameRequestListener
at com.camilogo1200.shoppinglist.presentation.fragments.ShoppingListNameRequestDialog.onAttach(ShoppinListNameRequestDialog.kt:68)
at androidx.fragment.app.Fragment.performAttach(Fragment.java:2922)
at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:464)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:275)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
This is my DialogFragment:
class ShoppingListNameRequestDialog : DialogFragment() {
private lateinit var listener: ShoppingListNameRequestListener
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
val inflater: LayoutInflater = requireActivity().layoutInflater
val requestNameView = inflater.inflate(R.layout.shopping_list_name_request_dialog, null)
val nameInput = requestNameView.findViewById<TextView>(R.id.shopping_list_dialog_input)
var listName = ""
builder.setView(requestNameView)
.setPositiveButton(R.string.save_shopping_list,
DialogInterface.OnClickListener {dialog, id ->
if(nameInput.text.toString() != "")
listName = nameInput.text.toString()
listener.onDialogPositiveClick(this,listName);
})
.setNegativeButton(R.string.cancel,
DialogInterface.OnClickListener{dialog, id ->
listener.onDialogNegativeClick(this)
})
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
interface ShoppingListNameRequestListener {
fun onDialogPositiveClick(dialog: DialogFragment,listName:String)
fun onDialogNegativeClick(dialog: DialogFragment)
}
override fun onAttach(context: Context) {
super.onAttach(context)
try {
listener = context as ShoppingListNameRequestListener
} catch (e: ClassCastException) {
throw ClassCastException((context.toString() +
"must implement ShoppingListNameRequestListener"))
}
}
This is my "CreateShoppingListMenuFragment" (the host fragment where I'm inflating the dialog):
class CreateShoppingListMenuFragment : Fragment(),
ShoppingListNameRequestDialog.ShoppingListNameRequestListener {
private lateinit var binding: FragmentCreateShoppingListMenuBinding
private val viewModel: CreateShoppingListMenuViewModel by activityViewModels()
private val args: CreateShoppingListMenuFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
layoutInflater,
R.layout.fragment_create_shopping_list_menu,
container,
false
)
binding.lifecycleOwner = this
viewModel.createItems()
viewModel.viewState.observe(viewLifecycleOwner, ::handleViewState)
val login = args.ownerName
val listId = args.listId
viewModel.setOwnerAndList(login, listId)
binding.createItemButton.setOnClickListener {
val directionToFragment =
CreateShoppingListMenuFragmentDirections.actionCreateShoppingListMenuFragmentToCreateItemMenuFragment(
login,
listId
)
Navigation.findNavController(binding.root).navigate(directionToFragment)
}
binding.completeShoppingListButton.setOnClickListener {
showNoticeDialog()
}
return binding.root
}
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
override fun onDialogPositiveClick(dialog: DialogFragment,listName: String) {
val result = viewModel.saveShoppingList(listName)
Log.i("shoppingListResult", "$result")
// travel to final fragment sent shoppinglist as arg
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
// User touched the dialog's negative button
}
private fun handleViewState(viewState: CreateShoppingListMenuViewState) {
when (viewState) {
is CreateShoppingListMenuViewState.ErrorViewState -> showError(viewState.exception as ShoppingException)
//is RegisterViewState.SuccessViewState ->showSuccess()
else -> showSuccess(viewState)
}
}
private fun showSuccess(viewState: CreateShoppingListMenuViewState) {
val receivedList = viewState as CreateShoppingListMenuViewState.SuccessViewState
val dataList = receivedList.data
val adapter = ShoppingListMenuAdapter(dataList, viewModel::changeItemCount)
binding.itemListArray.adapter = adapter
}
private fun showError(exception: ShoppingException) {
if (exception.idError as? ItemError == ItemError.NO_ITEMS_CREATED) {
val message = getString(R.string.no_items_created_error_messages)
Toast.makeText(activity, message, Toast.LENGTH_LONG).show()
}
Any help would be greatly appreciated!
The Context in onAttach(Context context) is context Activity fragment does not have its own Context .
The problem here is you are casting context to ShoppingListNameRequestListener for this to work your Activity needs to implement the listener .
To solve this problem there are several ways. if we go with your approach we can pass fragment instance as targetFragment and use it as listener inside the DialogFragment .
private fun showNoticeDialog() {
val dialog = ShoppingListNameRequestDialog()
dialog.setTargetFragment(this)
dialog.show(parentFragmentManager, "ShoppingListNameRequestDialog")
}
Then inside dialog you can do something like this .
class ShoppingListNameRequestDialog:DialogFragment(){
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
targetFragment?.let {
listener = it as ShoppingListNameRequestListener
}
}
}
However setTargetFragment is deprecated now. As a Alternate way you can do the same with a shared ViewModel or with the new API FragmentResultListener.
Need to paste context in my constructor in SwipeToDeleteCallback, which is located in the fragment:
private val swipeHandler = object : SwipeToDeleteCallback() {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
presenter.removeListItem(viewHolder.adapterPosition)
}
}
Tried to insert requireContext() but catch KotlinNullPointerException.
Maybe I can somehow use the context here viewHolder.context?
Here is some code SwipeToDeleteCallback:
abstract class SwipeToDeleteCallback(context: Context) :
ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
private val deleteIcon = context.getDrawableCompat(R.drawable.ic_log_out)
Also i called my fragment from MainActivity:
override fun onPostResume() {
super.onPostResume()
replaceFragment(TargetsFragment())
}
override fun showListOfTarget() {
replaceFragment(TargetsFragment())
}
Here is full code of my fragment:
class TargetsFragment : Fragment(), ListItemClickListener, SelectTargetViewContract, BaseDataSetContract {
private var recyclerView: RecyclerView? = null
private val presenter = TargetsPresenter(this)
private var adapter = TargetsAdapter(clickListener = this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_target_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter.setInitialData()
setupViews()
updateListData()
}
override fun onListItemClick(itemIndex: Int, itemCode: String) {
presenter.onListItemClick((adapter.getItem(itemIndex) as Target).guid)
}
override fun showTarget(guid: String) {
activity?.addFragment(TargetEditFragment.newInstance(guid))
}
override fun dataSetChanged() {
updateListData()
adapter.notifyDataSetChanged()
}
override fun updateViewContent() {
adapter.data = presenter.targetList
recyclerView?.adapter = adapter
recyclerView?.setVisible(presenter.shouldShowContent())
emptyView?.setVisible(presenter.shouldShowEmptyView())
}
private fun updateListData() {
if (presenter.firebaseUser == null) {
Log.d("some", "loadLogInView")
} else {
presenter.getTargetsFromDb()
}
}
private val swipeHandler = object : SwipeToDeleteCallback() {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
presenter.removeListItem(viewHolder.adapterPosition)
}
}
private fun setupViews() {
recyclerView = view?.findViewById(R.id.recyclerView)
recyclerView?.layoutManager = LinearLayoutManager(activity)
ItemTouchHelper(swipeHandler).attachToRecyclerView(recyclerView)
}
}
You can use
yourViewHolder.itemView.context
to get the context of yourViewHolder
After clarification from OP:
Initially I thought you only wanted to get context from your ViewHolder, but here's an addition to my answer:
Try moving your code which requires context to onAttach, which you will have to override, from the documentation : https://developer.android.com/guide/components/fragments.html
Caution: If you need a Context object within your Fragment, you can call getContext(). However, be careful to call getContext() only when the fragment is attached to an activity. When the fragment isn't attached yet, or was detached during the end of its lifecycle, getContext() returns null.
The method you're looking for looks like this :
override fun onAttach(context: Context) {
super.onAttach(context)
}
You will probably be safe to move all your code which requires context inside this method and assign it as you need to
i have an issue with an app that use ViewPager for display fragment. All works fine until the app goes in background and be killed from OS. It seems that after restore i have 2 IncidentScreenFragment that handle events, one with a null presenter (MVP) that crash my app.
My HomeActivity looks like:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter.onViewCreated()
initViews(savedInstanceState)
}
private fun initViews(savedInstanceState: Bundle?){
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
initFragment()
initMenu()
}
private fun initFragment(){
homeFragment = HomeScreenFragment.newInstance()
incidentFragment = IncidentScreenFragment.newInstance()
chatFragment = ChatFragment.newInstance()
weatherFragment = WeatherFragment.newInstance()
viewPager.adapter = ViewPagerAdapter(supportFragmentManager, this)
viewPager.offscreenPageLimit = 4
viewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {bottom_navigation.currentItem = position}
})
}
override fun getFragmentByPos(pos: Int): Fragment {
return when(pos){
0 -> homeFragment
1 -> incidentFragment
2 -> chatFragment
3 -> weatherFragment
else -> {
homeFragment
}
}
}
And my Adapter:
class ViewPagerAdapter internal constructor(fm: FragmentManager, activity:infinite_software.intelligence_center.intelligencecenter.ui.home.FragmentManager) : FragmentPagerAdapter(fm) {
private val COUNT = 4
private val activity = activity
override fun getItem(position: Int): Fragment{
var fragment: Fragment? = null
when (position) {
0 -> fragment = activity.getFragmentByPos(0)
1 -> fragment = activity.getFragmentByPos(1)
2 -> fragment = activity.getFragmentByPos(2)
3 -> fragment = activity.getFragmentByPos(3)
}
return fragment!!
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
override fun getCount(): Int {
return COUNT
}
override fun getPageTitle(position: Int): CharSequence? {
return "Section " + (position + 1)
}
}
Each Fragment have a static method that return new Fragment:
companion object {
fun newInstance(): HomeScreenFragment {
return HomeScreenFragment()
}
}
When the app has been killed in background i figure out that there is 2 objects (Fragment) that listen to event, one with Presenter correctly instantiate and one without.
Below my abstract BaseFragment class:
abstract class BaseFragment<P : BasePresenter<BaseView>> : BaseView,Fragment() {
protected lateinit var presenter: P
override fun getContext(): Context {
return activity as Context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter = instantiatePresenter()
}
override fun showError(error: String) {
(activity as BaseActivity<BasePresenter<BaseView>>).showError(error)
}
override fun showError(errorResId: Int) {
(activity as BaseActivity<BasePresenter<BaseView>>).showError(errorResId)
}
abstract fun onBackPressed(): Boolean
/**
* Instantiates the presenter the Fragment is based on.
*/
protected abstract fun instantiatePresenter(): P
abstract val TAG: String
Incident Fragment code:
class IncidentScreenFragment: BaseFragment<IncidentScreenPresenter>(), BaseView, IncidentView, AlertFilterListener, AlertItemClickListener, IncidentDetailListener {
var rvAdapter : IncidentAdapter? = null
var state : Int = LIST_STATE
override fun instantiatePresenter(): IncidentScreenPresenter {
return IncidentScreenPresenter(this)
}
override val TAG: String
get() = "INCIDENT"
override fun getContext(): Context {
return activity as Context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_incident, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
presenter.onViewCreated()
initObserve()
}
private fun initViews(){
//Reclycler view
alertRV.layoutManager = LinearLayoutManager(context)
rvAdapter = IncidentAdapter(ArrayList(), context, this)
alertRV.adapter = rvAdapter
//Apply Listeners
headerBox.setFilterListener(this)
incidentDetailView.setListener(this)
}
override fun initObserve() {
//Init observe presenter model
val alertObserver = Observer<ArrayList<AlertModel>> { alerts ->
Timber.d("Data received from Presenter [$alerts]")
showAlertList(alerts)
}
presenter.filteredAlertList.observe(context as BaseActivity<BasePresenter<BaseView>>,alertObserver)
}
override fun updateThisFilters(boxState: Boolean, level: Int) {
presenter.updateFilterList(boxState,level)
}
fun showOnlyThisLevel(level:Int){
presenter.showOnlyThisLevel(level)
headerBox.disableBoxExcept(level)
}
fun showAlertList(list: ArrayList<AlertModel>){
rvAdapter?.updateData(list)
}
override fun onItemClick(model: AlertModel) {
presenter.loadAlertDetail(model)
}
override fun showAlertDetail(model: AlertModel) {
incidentDetailView.setUpFromModel(model)
WhiteWizard.slideLeftEffect(incidentDetailView,incidentListRootElement)
state = DETAIL_STATE
}
override fun onbackFromDetailPressed() {
WhiteWizard.slideRightEffect(incidentListRootElement,incidentDetailView)
state = LIST_STATE
}
override fun showLoader() {
loaderIncident.visibility = View.VISIBLE
}
override fun hideLoader() {
loaderIncident.visibility = View.INVISIBLE
}
override fun onBackPressed(): Boolean {
when(state){
LIST_STATE -> return false
DETAIL_STATE -> {
onbackFromDetailPressed()
return true
}
else -> return false
}
}
fun newInstance(): IncidentScreenFragment {
return IncidentScreenFragment()
}
}
When i click on the button in homePage to display fragment content i got:
Process: XXXXXX, PID: 3192
kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
at infinite_software.intelligence_center.intelligencecenter.base.BaseFragment.getPresenter(BaseFragment.kt:11)
at XXXXXX.ui.home.incidentScreen.IncidentScreenFragment.showOnlyThisLevel(IncidentScreenFragment.kt:78)
at XXXXXX.ui.home.HomeActivity.filterDataWithSeverity(HomeActivity.kt:110)
at XXXXXX.ui.home.homeScreen.HomeScreenFragment.filterBy(HomeScreenFragment.kt:76)
at XXXXXX.ui.home.homeScreen.HomeScreenFragment$initViews$5.onClick(HomeScreenFragment.kt:56)
If i try to print the id of Fragment, i obtain 2 different ids from method call showOnlyThisLevel() and onBackPressed(). What i miss ?
After doing some research, it seems that the problem stems from the misnaming of FragmentPagerAdapter's method - being named getItem(), but not clearly specifying that the abstract method getItem(int position) is supposed to return a new instance of a fragment rather than just "get an instance of one".
Of course, there is not much we can do about an incorrect name after it's been out in the wild for 7 years, but at least we can fix the bug that stems from this issue in your code ;)
Without further ado, the cause of your NPE is that onCreateView (where your Presenter is instantiated) is never called.
This happens because you are creating the fragment here:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
homeFragment = HomeScreenFragment.newInstance()
incidentFragment = IncidentScreenFragment.newInstance()
}
You return this fragment from inside getItem(int position) in your FragmentPagerAdapter:
override fun getItem(position: Int): Fragment = when(position) {
...
1 -> activity.incidentFragment
...
}
So what we know about activity.incidentFragment is that in it, onCreateView() is never called.
This is caused by the fact that it's never actually added to a FragmentManager and never displayed on the screen.
That's because super.onCreate(savedInstanceState) in Activity recreates all Fragments, using their no-args constructor, via reflection, while keeping their tag (see findFragmentByTag).
So as you can see in this answer, or as I can quote here:
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
The getItem(position) method is only called if the Fragment is not found by the fragment tag that the FragmentPagerAdapter sets for the fragment, which IS automatically recreated after low memory condition kills your app.
Therefore, YOUR new fragment (that you create by hand in the Activity) is NEVER used, and therefore it has no view, never initialized, never added to FragmentManager, it's not the same instance as what's actually inside your ViewPager, and it crashes when you call it. Boom!
Solution is to instantiate the Fragment inside FragmentPagerAdapter's getItem(position) method. To get an instance of the fragment, use this answer.
I created a bottom navigation activity in my project, which contains one activity and two fragments. In Main Activity I have value stored in a variable but if I pass the value to the fragments then I am getting NullPointer Exception error. I am using kotlin in my project and any help is appreciated.
Expectation
Get Value into Fragment from MainActivity. MainActivity--->TestOneFragment
Language Used
Kotlin
Main Activity
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener
{
private val KEY_POSITION = "keyPosition"
private var navPosition: BottomNavigationPosition = BottomNavigationPosition.ONE
private lateinit var toolbar: Toolbar
private lateinit var bottomNavigation: BottomNavigationView
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
restoreSaveInstanceState(savedInstanceState)
setContentView(R.layout.activity_test)
toolbar = findViewById(R.id.toolbar)
bottomNavigation = findViewById(R.id.bottom_navigation)
setSupportActionBar(toolbar)
initBottomNavigation()
initFragment(savedInstanceState)
var Name:String=intent.getStringExtra("name")
println("Test CLLicked: $Name")
//This code is to pass the value to Fragment
var bundle=Bundle()
bundle.putString("name",Name)
var frag=TestFragment()
frag.arguments=bundle
}
override fun onSaveInstanceState(outState: Bundle?)
{
outState?.putInt(KEY_POSITION, navPosition.id)
super.onSaveInstanceState(outState)
}
override fun onNavigationItemSelected(item: MenuItem): Boolean
{
navPosition = findNavigationPositionById(item.itemId)
return switchFragment(navPosition)
}
private fun restoreSaveInstanceState(savedInstanceState: Bundle?)
{
savedInstanceState?.also {
val id = it.getInt(KEY_POSITION, BottomNavigationPosition.ONE.id)
navPosition = findNavigationPositionById(id)
}
}
private fun initBottomNavigation()
{
bottomNavigation.active(navPosition.position)
bottomNavigation.setOnNavigationItemSelectedListener(this)
}
private fun initFragment(savedInstanceState: Bundle?)
{
savedInstanceState ?: switchFragment(BottomNavigationPosition.ONE)
private fun switchFragment(navPosition: BottomNavigationPosition): Boolean {
return supportFragmentManager.findFragment(navPosition).let {
if (it.isAdded) return false
supportFragmentManager.detach() // Extension function
supportFragmentManager.attach(it, navPosition.getTag()) // Extension function
supportFragmentManager.executePendingTransactions()
}
}
private fun FragmentManager.findFragment(position: BottomNavigationPosition): Fragment
{
return findFragmentByTag(position.getTag()) ?: position.createFragment()
}
}
TestOneFragment
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val testName= arguments!!.getString("name")
....
}
Error
kotlin.KotlinNullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2778)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2856)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1589)
Here is an example of the newInstance pattern for creating Fragments.
This is within a companion object, which is pretty much just a way to say "these things are Static."
First, you should define constants for your Bundle names, this will help keep everything aligned. Next, define a newInstance method that takes your parameters, such as the name.
And within there, you will create your Fragment and return it. This way, your Activity doesn't have to worry about the Bundle or anything. All your logic is within one place, for storing/retrieving, all within your Fragment.
class TestOneFragment {
companion object {
const val ARG_NAME = "name"
fun newInstance(name: String): TestOneFragment {
val fragment = TestOneFragment()
val bundle = Bundle().apply {
putString(ARG_NAME, name)
}
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val name = arguments?.getString(ARG_NAME)
// ...
}
}
And now, you can easily get your Fragment by doing the following.
class Test : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
// ...
val name = intent.getStringExtra("name")
// Creating the new Fragment with the name passed in.
val fragment = TestFragment.newInstance(name)
}
}
Hopefully that helps!
I have a ViewPager. I want to change page when fragment allow it to do.
This is the onClick when I want to check.
when (currentPage) {
0 -> {
if ((mAdapter.getItem(currentPage) as NameFragment).canGo()) {
mViewPager.setCurrentItem(currentPage + 1, true)
}
}
1 -> ...
}
My Fragment:
private lateinit var fName: EditText
private lateinit var lName: EditText
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = UI {
verticalLayout {
fName = editText {
hint = "FIRST NAME"
textSize = 20f
}
lName = editText {
hint = "LAST NAME"
textSize = 20f
}
}
}
fun canGo(): Boolean {
val firstName = fName.text.toString()
val lastName = lName.text.toString()
if (firstName.isEmpty() || firstName.isBlank() || lastName.isBlank() || lastName.isEmpty()) {
toast("First name or Last name cannot be empty")
return false
}
return true
}
I initialize fName and lName on onCreateView method and this is the error I get. Full exception is here.
kotlin.UninitializedPropertyAccessException: lateinit property fName has not been initialized
These properties need to be initialised in the onViewCreated method, not onCreateView.
onCreateView is only responsible for creating the view as the name suggests, while onViewCreated is responsible for handling events after your fragment is properly created.
And if you need, you can create a callback that will be triggered when your fragment view is created, then you implement that callback in the activity, so that you will only access that canGo method after the fragment is created. Again, only if you really need to.
interface OnFragmentInflatedListener {
fun onFragmentInflated(fragment: Fragment)
}
// Fragment
private var onFragmentInflatedListener: OnFragmentInflatedListener? = null
fun setOnFragmentInflatedListener(onFragmentInflatedListener: OnFragmentInflatedListener?) {
this.onFragmentInflatedListener = onFragmentInflatedListener
}
// Fragment's onViewCreated
// initialise your properties here
onFragmentInflatedListener?.onFragmentInflated(this)
// Activity
class MyActivity : AppCompatActivity(), OnFragmentInflatedListener {
#Override
fun onCreate(savedInstanceState: Bundle) {
// do your stuff
myFragment.setOnFragmentInflatedListener(this)
}
#Override
fun onFragmentInflated(fragment: Fragment) {
// do what you wanted to do
}
}
I found the answer on this article. First I edited my Adapter:
private class Adapter(fm: FragmentManager, private val fragments: ArrayList<Fragment>) : FragmentStatePagerAdapter(fm) {
private val instantiatedFragments = SparseArray<WeakReference<Fragment>>()
override fun getItem(position: Int): Fragment = fragments[position]
override fun getCount() = fragments.size
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
instantiatedFragments.put(position, WeakReference(fragment))
return fragment
}
#Nullable
fun getFragment(position: Int) = instantiatedFragments.get(position)?.get()
}
And also my onClick method
when (currentPage) {
0 -> {
if ((mAdapter.getFragment(currentPage) as NameFragment).canGo()) {
mViewPager.setCurrentItem(currentPage + 1, true)
}
}
1 -> ...
}
I changed getItem(currentPage) to getFragment(currentPage).