Kotlin - interface not initialized when passing data between fragments - android

I am new to android development and making a simple To-do list app to get my bearings.
I have a fragment from my MainActivity that contains a list, called FragToDoList. I also have a class DialogAddToDo that extends DialogFragment to bring up a dialog to input your to do list item infomation. I am having trouble passing the data entered into DialogAddTodo back to the FragToDoList class to then display it in the list.
It is saying my interface is not initialized, and cant find a way to resolve this issue. Does anyone have some pointers?
class FragToDoList : Fragment(), DialogAddTodo.ToDoAddedListener {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private val toDoList: MutableList<ToDoItem>? = null
lateinit var adapter: ToDoAdapter
private lateinit var listViewItem: ListView
lateinit var mContext: Context
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_frag__to_do_list, container, false)
listViewItem = view.findViewById(R.id.listView)
//for showing items in list view
// adapter = ToDoAdapter(mContext, toDoList!!)
// listViewItem.adapter = adapter
val fab = view.findViewById<FloatingActionButton>(R.id.fabAddTask)
fab?.setOnClickListener {
showAddTaskDialog(childFragmentManager);
}
// Inflate the layout for this fragment
return view
}
private fun showAddTaskDialog(childFragmentManager:FragmentManager) {
var dialog = DialogAddTodo()
dialog.show(childFragmentManager,DialogAddTodo.TAG)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment FragTodoList.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
FragToDoList().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
override fun onToDoAdded(item: ToDoItem) {
toast("added",mContext)
}
}
class DialogAddTodo : DialogFragment() {
interface ToDoAddedListener {
fun onToDoAdded(item: ToDoItem)
}
private lateinit var onTodoAdded: ToDoAddedListener
private val todoItemModel: TodoItemModel? =null
companion object {
const val TAG = "Dialog Add Task"
}
lateinit var mContext: Context
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.dialog_add_task, container, false)
var addBut = view.findViewById<Button>(R.id.butAddTaskDialog)
addBut.setOnClickListener()
{
val todoItemData = ToDoItem.createToDoItem()
var taskNameText =
view.findViewById<TextView>(R.id.editTextTaskName)
if (taskNameText != null)
todoItemData.itemDataText = taskNameText?.text.toString()
todoItemData.done = false
todoItemData.UID = getRandomString(Int.SIZE_BITS - 1)
// Frag_ToDoList().adapter.notifyDataSetChanged()
//b.setItem(todoItemData)
//todoItemModel.setItem(todoItemData)
onTodoAdded.onToDoAdded(todoItemData)
this.dismiss()
}
return view
}
}

You declared lateinit variable
private lateinit var onTodoAdded: ToDoAddedListener
however it remains uninitialized.
When uninitialized var accessed it throws an exception.
You need to assign variable for example using accessor:
var dialog = DialogAddTodo()
// Initialize listener
dialog.setListener(this)
dialog.show(childFragmentManager,DialogAddTodo.TAG)
In DialogAddTodo add following method:
fun setListener(onTodoAdded: ToDoAddedListener) {
this.onTodoAdded = onTodoAdded
}

Related

How to use setOnClickListener in fragment

I ran into a problem, I put some buttons in my fragment and in the code file wanted to use them but I had a problem. Do you know some methods of how can I solve it? I use Kotlin
I tried a lot of variants on how to use setOnClickListener in fragments but it didn't work for me. My code looks like this:
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class WorkoutFragment : Fragment(),WorkoutAdapter.OnItemClickListener, WorkoutFragmentView {
private lateinit var presenter: WorkoutFragmentPresenter
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v = inflater.inflate(R.layout.fragment_workout, container, false)
return v
}
companion object {
#JvmStatic
fun newInstance(param1: String, param2: String) =
WorkoutFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
private fun InitRecyclerView(v:View) {
val recyclerView = v.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = WorkoutAdapter(WorkoutProvider.WorkoutList, this)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
InitRecyclerView(view)
presenter = WorkoutFragmentPresenter(this)
}}
I answered this yesterday here.
Passing the fragment's view into the adapter isn't the best solution IMHO, it's better to pass a click lambda as demonstrated in the above example. In any case, all you have to do after that is just set an onClickListener in the adapter's items.

Can Fragment act like MainActivity?

Project1
I have created a app that scans nearby wifidirect enabled devices whose UI was simple and had only one layout(activitymain.xml) and the code was in MainActivity.java & WifiDirectBroadcastReceiver. (Code can be found here: Can't find nearby WiFi- Direct devices showing "No Device Found!")
Project2
Now, I want to use Tablayout(custom not from default) which contains 2 tabs so I have to use 2 fragments.
Where should I place the code that was in MainActivity(project1)?
should I copy to fragment1 or MainActivity(Project2)
You have to copy the code of MainActivity (project1) to fragment of the tabbed layout. And then configure the SectionPagerAdapter like below.
Also you have to change some code of your MainActivity so that it gets fitted into the fragment.
private val TAB_TITLES = arrayOf(
R.string.tab_text_1,
R.string.tab_text_2
)
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
class SectionsPagerAdapter(private val context: Context, fm: FragmentManager)
: FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
var fragment: Fragment? = null
when (position) {
0 -> fragment = Fragment1("f1","f1")
1 -> fragment = Fragment2("f2","f2")
}
return fragment!!
}
override fun getPageTitle(position: Int): CharSequence? {
return context.resources.getString(TAB_TITLES[position])
}
override fun getCount(): Int {
// Show 2 total pages.
return 2
}
}
You can create fragments like this:
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class Fragment1 : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var _binding: Fragment1Binding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onPause() {
super.onPause()
}
override fun onResume() {
super.onResume()
}
companion object {
#JvmStatic
fun newInstance(param1: String, param2: String) =
HomeFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
You can make Fragment2 like this and attach this to your tabbed layout.

Why return function in variable is null? Kotlin + Android

I am missing some basic coding knowledge here I think, I want to present value to the fragment by assigning the function to the variable in a viewModel. When I call the function directly, I get correct value. When I assign function to variable and pass the variable to the fragment it is always null, why?
View Model
class CartFragmentViewModel : ViewModel() {
private val repository = FirebaseCloud()
private val user = repository.getUserData()
val userCart = user?.switchMap {
repository.getProductsFromCart(it.cart)
}
private fun calculateCartValue(): Long? {
val list = userCart?.value
return list?.map { it.price!! }?.sum()
}
//val cartValue = userCart?.value?.sumOf { it.price!! } <- THIS will be null
val cartValue = calculateCartValue() <- THIS will be null
val cartSize = userCart?.value?.size <- THIS will be null
}
Fragment
class CartFragment : RootFragment(), OnProductClick, View.OnClickListener {
private lateinit var cartViewModel: CartFragmentViewModel
private lateinit var binding: FragmentCartBinding
private val cartAdapter = CartAdapter(this)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_cart,
container,
false
)
setAnimation()
cartViewModel = CartFragmentViewModel()
binding.buttonToCheckout.setOnClickListener(this)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.recyclerCart.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = cartAdapter
}
cartViewModel.userCart?.observe(viewLifecycleOwner, { list ->
cartAdapter.setCartProducts(list)
updateCart()
})
}
override fun onClick(view: View?) {
when (view) {
binding.buttonToCheckout -> {
navigateToCheckout(cartViewModel.cartValue.toString())
cartViewModel.sendProductEvent(
cartAdapter.cartList,
ProductEventType.CHECKOUT
)
}
}
}
override fun onProductClick(product: Product, position: Int) {
cartViewModel.removeFromCart(product)
cartAdapter.removeFromCart(product, position)
updateCart()
}
private fun updateCart() {
binding.textCartTotalValue.text = cartViewModel.cartValue.toString() <- NULL
binding.textCartQuantityValue.text = cartViewModel.cartSize.toString() <- NULL
}
}
Thanks!
It looks like userCart is some sort of observable variable which initially holds a null value and then gets populated with the data from your repository after the network call (or something similar) completes.
The reason that all your variables are null are because you are declaring their value immediately, so by the time those statements get executed, the network call hasn't yet completed and userCart?.value is null. However calling the calculateCartValue() function later on in the code might yield a value if the fetch is complete.

Android: Mock a Context for Fragment unit testing

I have a fragment that uses the delegation pattern when a button is clicked.
class FutureMeetingEventViewFragment #Inject constructor(): Fragment() {
#Inject
lateinit var bundleUtilityModule: BundleUtilityModule
lateinit var parcelableMeetingEvent: ParcelableMeetingEvent
private var delegate: IEventDetailsDelegate? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if(context is IEventDetailsDelegate) {
delegate = context
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
val binding: ViewDataBinding = DataBindingUtil.inflate<ViewDataBinding>(inflater, R.layout.layout_future_meeting_event_fragment, container,false)
DaggerUtilityModuleComponent.create().inject(this)
this.parcelableMeetingEvent = this.bundleUtilityModule.getTypeFromBundle(BundleData.MEETING_EVENT_DATA.name, arguments)
binding.setVariable(BR.meetingEvent, this.parcelableMeetingEvent)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
this.setCTAClickEvent()
}
private fun setCTAClickEvent() {
future_event_card.setOnClickListener {
delegate?.onEventClicked(this, this.parcelableMeetingEvent)
}
}
}
A problem I can see in the unit test is that when I click the button, because the IEventDetailsDelegate field will be null, the test will always fail. My unit test so far is simply testing if the correct data is displayed on the view:
#RunWith(AndroidJUnit4::class)
class GivenAFutureMeetingEventFragmentIsDisplayed {
private var fragmentId: Int = 0
private lateinit var fragmentArgs: Bundle
private lateinit var scenario: FragmentScenario<FutureMeetingEventViewFragment>
#Before
fun setup() {
fragmentId = FutureMeetingEventViewFragment().id
fragmentArgs = Bundle()
fragmentArgs.apply {
putParcelable(BundleData.MEETING_EVENT_DATA.name, ParcelableMeetingEvent(
"Test Description",
"Test Summary",
"12344556",
DateTime("2020-03-14T17:57:59+00:00"),
DateTime("2020-03-14T18:57:59+00:00"),
"Somewhere across the universe"
))
}
scenario = launchFragmentInContainer<FutureMeetingEventViewFragment>(
fragmentArgs,
fragmentId
)
}
#Test
fun thenACorrectlyMappedMeetingEventShouldBePassedToTheFragment() {
onView(withId(R.id.spec_future_meeting_event_start_day)).check(matches(withText("Saturday")))
onView(withId(R.id.spec_future_meeting_event_start_time)).check(matches(withText("5:57:59 PM")))
onView(withId(R.id.spec_future_meeting_event_end_time)).check(matches(withText("6:57:59 PM")))
onView(withId(R.id.spec_future_meeting_event_summary)).check(matches(withText("Test Summary")))
onView(withId(R.id.spec_future_meeting_event_description)).check(matches(withText("Test Description")))
}
#Test
fun thenTheContainerShouldBeClickable() {
onView(withId(R.id.future_event_card)).check(matches(isClickable()))
}
}
I guess there's two questions in this post:
Can I mock out a context that implements the IEventDetailsDelegate and assign it to my mock fragment?
Should I test the event on an actual activity that implements the interface?

Fragments and Activities using Kotlin

I have the following activity with two integers
class ComplexActivity : AppCompatActivity() {
var clubs : Int = 0
var diamonds : Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_complex)
val fragment = ClubsFragment()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.main_frame, fragment)
transaction.commit()
}
}
I want to change the value of the integer clubs from the fragment ClubsFragment when isScored is true
class ClubsFragment : Fragment(), SeekBar.OnSeekBarChangeListener{
private var isScored = false
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val v = inflater!!.inflate(R.layout.fragment_clubs, container, false)
v.image_clubs.setOnClickListener {
if(isScored){
activity.clubs = 4
}
}
}
}
I tried to use activity.clubs but It's not working. How can I access the activity constants from a fragment.
You would create an interface, let's say FragmentListener for your Activity that contains a function like fun updateClubs(count: Int). Your Activity should implement this interface.
Then, in your Fragment, add a fragmentListener property and override onAttach(context: Context):
private var fragmentListener: FragmentListener? = null
override fun onAttach(context: Context) {
this.listener = context as? FragmentListener
}
Then, in your OnClickListener, you can simply call fragmentListener?.updateClubs(4).

Categories

Resources