Problem Summary
My main activity is called MapsActivity.kt
I have a location permission check that is inside mapFragment.kt
When I try to pass the context of MapsActivity to the method isGpsEnable() which is inside mapFragment then I get the error Unresolved Reference:#MapsActivity
I have a splashscreen SplashActivity.kt which has the in the Manifest
What I have tried
The activity android:name is correct in the manifest
I have tried both this#MapsActivity and this#SplashActivity
Package name is **correct*
Restart/Invalidate etc
Used requireContext() and it works but don't know if its the correct way to solve this
Call isGpsEnable() method in OnMapReady
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="gr.mantis_project.obdLogger">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
<activity
android:name="gr.example.obdLogger.SplashActivity"
android:label="#string/title_activity_maps"
android:theme="#style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="gr.example.obdLogger.MapsActivity">
</activity>
</application>
</manifest>
MapsActivity.kt
package gr.example.obdLogger
class MapsActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnMapClickListener {
override fun onMapReady(p0: GoogleMap?) {
}
override fun onMapClick(p0: LatLng?) {
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivitymainBinding =
DataBindingUtil.setContentView(this, R.layout.mapsactivity)
setSupportActionBar(findViewById(R.id.toolbar))
}
mapFragment.kt
package gr.example.obdLogger
class MapFragment : Fragment(), OnMapReadyCallback {
private lateinit var mMapView: MapView
private lateinit var mMap: GoogleMap
private lateinit var mView: View
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var lastLocation: Location
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
mMapView.onSaveInstanceState(outState)
isGpsEnable()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_map, container, false)
mMapView = view?.findViewById(R.id.mapview) as MapView
mMapView.onCreate(savedInstanceState)
mMapView.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onResume() {
super.onResume()
mMapView.onResume()
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
mMap.uiSettings.isZoomControlsEnabled = true
}
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1
}
private fun isGpsEnable(): Boolean {
if (ActivityCompat.checkSelfPermission(
requireContext(), // **This works**
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this#MapsActivity, // **This is unresolved**
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
),
LOCATION_PERMISSION_REQUEST_CODE
)
} else {
Log.e("DB", "PERMISSION GRANTED")
}
return true
}
}
There's no inner dependency from MapFragment to MapsActivity, so you can not reference MapsActivity as outer reference. An outer reference only works for inner classes (note that lambda functions are anonymous inner clases, therefore you can also use this# there to reference the outer scope). If you need a context in your app you can call the nullsafe requireContext() method within MapFragment or reference the instance of MapsActivity through
(activity as MainActivity)
A fragment has access to the activity it is attached to through its property activity this why (activity as MainActivity) works
When working with nullable values I like using the ? operator chained with a lete.g.
context?.let{ doStuff(it) } That way you're safe that you won't get nullptr exceptions
It won't work. MapsActivity isn't the outer scope of your fragment. You can read a bit more about qualified this here
Related
I have created a home fragment in which I am using imageslider.
This is the xml file
<com.denzcoskun.imageslider.ImageSlider
android:id="#+id/slide"
android:layout_width="match_parent"
android:layout_height="250dp"
app:auto_cycle="true"
app:delay="0"
app:period="1000"
app:corner_radius="20"
tools:ignore="MissingConstraints" />
and this the HomeFragment.kt file
class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val imageList= ArrayList<SlideModel>()
imageList.add(SlideModel("https://www.pixelstalk.net/wp-content/uploads/2016/07/A-Images-HD-Screen-Download.jpg"))
imageList.add(SlideModel("https://media.istockphoto.com/photos/message-in-a-glass-bottle-on-a-beach-at-sunset-picture-id184699888?k=20&m=184699888&s=170667a&w=0&h=SJVDAKTmhEHeEdAKqFsW2Wjx3fQ1DLzX5CPHkiclSMs="))
val imageSlider= findViewById<ImageSlider>(R.id.slide)
imageSlider.setImageList(imageList, ScaleTypes.FIT)
}
}
I am having an error "unresolved reference findViewById " and also the app is crashing.
As clarified by #ADM made some changes and it worked..
class HomeFragment : Fragment(R.layout.fragment_home) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val imageList= ArrayList<SlideModel>()
imageList.add(SlideModel("https://www.pixelstalk.net/wp-content/uploads/2016/07/A-Images-HD-Screen-Download.jpg"))
imageList.add(SlideModel("https://media.istockphoto.com/photos/message-in-a-glass-bottle-on-a-beach-at-sunset-picture-id184699888?k=20&m=184699888&s=170667a&w=0&h=SJVDAKTmhEHeEdAKqFsW2Wjx3fQ1DLzX5CPHkiclSMs="))
val imageSlider= view.findViewById<ImageSlider>(R.id.slide)
imageSlider.setImageList(imageList, ScaleTypes.FIT)
}
}
Very new to coding ~1 month. Apologies if this is answered elsewhere, but spent hours looking through other similar questions but cant get it quite right. What: Trying to pass the position click data from fragment 1 (MainFragment) to fragment 2 (MapsFragment) to load the corresponding map. Goes through the adapter onclick method because I want to use RecyclerView on MainFragment. Project is buildable but crashes when I click an item on recyclerview (cant open map). Whats wrong????
Thanks ahead of time!!
EDIT: ANSWERED
Thank you everyone for comments. Was able to solve this by 1) changing lateinit var on mapsfragment to a regular var + nullable, and 2) changing my bundle line in the main fragment to
val bundle = bundleOf(EXTRA_USER_MAP to sports[position])
view.findNavController(.navigate(R.id.action_mainFragment_to_mapsFragment, bundle)
woohoo Kotlin!
Main Fragment:
const val EXTRA_USER_MAP = "EXTRA_USER_MAP"
private const val TAG = "MainFragment"
class MainFragment : Fragment() {
override fun onCreateView ... // code to inflate my fragment view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sports = generateData()
myMaps.layoutManager = LinearLayoutManager(activity)
myMaps.adapter = this.context?.let {
MapsAdapter(it, sports, object: MapsAdapter.OnClickListener {
override fun onItemClick(position: Int) {
Log.i(TAG, "onItemClick $position")
// When user taps on view in recyclerview, navigate to new fragment and opens corresponding map
val bundle = Bundle()
bundle.putSerializable(EXTRA_USER_MAP, sports[position])
view.findNavController().navigate(R.id.action_mainFragment_to_mapsFragment)
}
})
}
}
private fun generateData(): List<Sport> { ... // code for my data set
Maps Fragment
private const val TAG = "MapsFragment"
class MapsFragment : Fragment(), OnMapReadyCallback {
private lateinit var map: GoogleMap
private lateinit var sport: Sport
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_maps, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bundle = arguments
if (bundle !=null) {
sport = bundle.getSerializable(EXTRA_USER_MAP) as Sport
}
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(this)
}
// Actions for map
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
Log.i(TAG, "Map rendered: ${sport.title}") // Logcat check once working
...
Related Adapter code
class MapsAdapter(
val context: Context,
val sports: List<Sport>,
val onClickListener: OnClickListener)
: RecyclerView.Adapter<MapsAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
interface OnClickListener {
fun onItemClick(position: Int)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val sport = sports[position]
holder.itemView.setOnClickListener {
Log.i(TAG, "Tapped on position $position")
onClickListener.onItemClick(position)
}
...
You can have a shared view model where you can have a LiveData property and you can change his value from MainFragment and listen to his changes from MapsFragment and do everything you want or you can have a listener and can listen when user click and get the position. Hope this will helpful for you
I am trying to pass click event on a view in a Fragment to the Activity. I was following documentation guide from here.
As I understand it and I could be wrong but we need to create an interface, add a method declaration to it and trigger the method from when the click event is received on the Fragment. The Activity should then implement the interface defined in the fragment, so that the activity receives that event.
I have my Fragment:
class MoreFragment : Fragment() {
internal var callback: OnMoreItemClickedListener
fun setOnMoreItemClickedListener(callback: OnMoreItemClickedListener) {
this.callback = callback
}
interface OnMoreItemClickedListener {
fun onAddClothClicked()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_more, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
}
override fun onStart() {
super.onStart()
btn_add_clothes.setOnClickListener {
callback.onAddClothClicked()
}
}
}
And I have my Activity:
class MainActivity : AppCompatActivity(), MoreFragment.OnMoreItemClickedListener {
override fun onAddClothClicked() {
Log.d("MainActivity", "HERE")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_clothes,
R.id.navigation_seasons,
R.id.navigation_notifications,
R.id.navigation_more
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
override fun onAttachFragment(fragment: Fragment) {
super.onAttachFragment(fragment)
if(fragment is MoreFragment){
fragment.setOnMoreItemClickedListener(this)
}
}
}
Now the error that I am getting is:
MoreFragment.kt: (16, 5): Property must be initialized or be abstract
MoreFragment.kt: (16, 5) is internal var callback: OnMoreItemClickedListener.The doc link above does suggest the same/ similar code but it does not work.
Things that I have tried
I have tried putting a lateinit var for the callback: OnMoreItemClickedListener variable there but no luck. As it never gets initialized.
I put internal var callback: OnMoreItemClickedListener? = null and referenced callback using the null safe operator ?. but was not able to get the click event.
I am fairly new to Kotlin and Fragments too so please help me figure how to do this. Thanks
In Fragment override onAttach method
private lateinit var callback: OnMoreItemClickedListener
override onAttach(context : Context){
callback = context as OnMoreItemClickedListener
}
then your callback will be init. as onAttach will be first. so no Property must be initialized or be abstract errror will occur.
also in Activity implement your interface(OnMoreItemClickedListener) and override its method. You will get callback
Your both way is looks OK
I have tried putting a lateinit var for the callback:
I put internal var callback: OnMoreItemClickedListener? =
null
I guess issue is here just debug below code. is it going inside if
if(fragment is MoreFragment){
fragment.setOnMoreItemClickedListener(this)
}
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)
}
}
}
I declared a child of MapActivity:
class RecordingActivity : MapActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("RecirdingActivity", "InitializeMap") //called
}
override fun getView(): Int {
return R.layout.activity_recording
}
}
I make a call to start this activity from my main activity:
fab.setOnClickListener {
Log.d("MainActivity", "fabClick") //called
startActivity(intentFor<RecordingActivity>())
}
and I have the abstract activity:
abstract class MapActivity: AppCompatActivity(), OnMapReadyCallback {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
setContentView(getView())
initializeMap()
Log.d("MapActivity", "InitializeMap")//not called
}
}
and onCreate method of this activity is never called
I traced it with a debugger and I had the same result.
What am I doing wrong?
there seems to be two solutions:
maybe the onCreate you actually want to override in MapActivity has the signature onCreate(android.os.Bundle):
abstract class MapActivity: AppCompatActivity(), OnMapReadyCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(getView())
initializeMap()
Log.d("MapActivity", "InitializeMap")
}
}
the documentation of the onCreate(android.os.Bundle, android.os.PersistableBundle) method that is being overridden in MapActivity suggests that persistableMode for the activity in the AndroidManifest.xml needs to be set to persistAcrossReboots for it to be called...but MapActivity is abstract, so you would need to set the attribute for its subclasses instead. in this case that would be RecordingActivity.
<?xml version="1.0" encoding="utf-8"?>
<manifest>
...
<application>
...
<activity
android:name=".RecordingActivity"
android:persistableMode="persistAcrossReboots"/>
...
</application>
...
</manifest>
Simple answer: method onCreate(savedInstanceState: Bundle?) and onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) is not the same.
You need to override the same method, probably the first one.