Android: GoogleMap callbacks not being called - android

I've started a new Android project based around a Google map using the Android Studio template, and my callbacks are not getting called at all.
Here is my activity code:
class MapsActivity : BaseActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val center = LatLng(/*Redacted*/)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 18.0F))
mMap.setOnCameraChangeListener { GoogleMap.OnCameraChangeListener {
val bounds = mMap.projection.visibleRegion.latLngBounds
} }
mMap.setOnMapClickListener { GoogleMap.OnMapClickListener {
val lat = it.latitude
} }
mMap.setOnCameraIdleListener { GoogleMap.OnCameraIdleListener {
val bounds = mMap.projection.visibleRegion.latLngBounds
} }
mMap.setOnCameraMoveListener { GoogleMap.OnCameraMoveListener {
val bounds = mMap.projection.visibleRegion.latLngBounds
} }
}
}
The contents of the callbacks aren't important at the moment, I'm setting breakpoints within them all and none of them are getting called.
Here's my app gradle file:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "redacted"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.google.android.gms:play-services-maps:15.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}
This might be a red herring, but occasionally I get an error in the console and the map stops working at all:
A/art: art/runtime/stack.cc:153] Check failed: success Failed to read the this object in void com.google.maps.api.android.lib6.gmm6.vector.gl.drawable.y.a(com.google.maps.api.android.lib6.gmm6.vector.gl.f, com.google.maps.api.android.lib6.gmm6.vector.camera.c, com.google.maps.api.android.lib6.gmm6.vector.m)
What am I doing wrong?
Edit:
Here's my manifest file as requested:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="redacted">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="#string/google_maps_key"/>
<activity
android:name=".MapsActivity"
android:label="#string/title_activity_maps">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Edit 2:
Here's my layout file:
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/map"
tools:context=".MapsActivity"
android:name="com.google.android.gms.maps.SupportMapFragment"/>

In your code the above breakpoint will hit.
Try this. In order for the function to execute you need to go by one of the following approaches.
class MapsActivity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnCameraMoveListener, GoogleMap.OnMapClickListener {
private lateinit var mMap: GoogleMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val center = LatLng(/*Redacted*/)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 18.0F))
mMap.setOnCameraMoveListener(this)
mMap.setOnMapClickListener(this)
}
override fun onCameraMove() {
val i = 0
val bounds = mMap.projection.visibleRegion.latLngBounds
}
override fun onMapClick(p0: LatLng?) {
val i = 0
val bounds = mMap.projection.visibleRegion.latLngBounds
}
}
Or another way to set listeners is as follows
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
val center = LatLng(/*Redacted*/)
mMap.setOnCameraMoveListener {
val bounds = mMap.projection.visibleRegion.latLngBounds
}
mMap.setOnMapClickListener {
val bounds = mMap.projection.visibleRegion.latLngBounds
}
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(center, 18.0F))
}

you have to initialize google api client in onCreate() method.
mGoogleApiClient = GoogleApiClient.Builder(this)
.addApi(Places.GEO_DATA_API)
.addApi(Places.PLACE_DETECTION_API)
.enableAutoManage(this, this)
.build()

Related

App crashes when getting current user's location (Kotlin)

My code compiles but the app will not start now. I've tried many variations to simply get a user's current location and display it using MapsSDK but nothing works.
Fragment Activity:
class BuyFragment : Fragment(), OnMapReadyCallback {
private var _binding: FragmentBuyBinding? = null
private val binding get() = _binding!!
private lateinit var map: GoogleMap
private lateinit var fusedLocationClient: FusedLocationProviderClient
private val locationPermissionCode = 1
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val view = inflater.inflate(R.layout.fragment_buy, container, false)
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireContext())
return view
}
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
locationPermissionCode
)
} else {
map.isMyLocationEnabled = true
getCurrentLocation()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == locationPermissionCode) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
map.isMyLocationEnabled = true
getCurrentLocation()
}
}
}
private fun getCurrentLocation() {
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
if (location != null) {
val currentLocation = LatLng(location.latitude, location.longitude)
map.addMarker(MarkerOptions().position(currentLocation).title("Current Location"))
map.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 15.0f))
}
}
}
}
Dependencies:
dependencies {
implementation 'androidx.preference:preference:1.0.0-rc02'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.6.0'
implementation 'com.google.android.gms:play-services-maps:18.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.2'
implementation 'com.google.android.gms:play-services-location:11.0.2'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'com.google.android.gms:play-services-location:21.0.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
layout:
<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"
tools:context=".ui.buy.BuyFragment">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment" />
</androidx.constraintlayout.widget.ConstraintLayout>
I keep reverting my code to just display maps and that works but I'd like to have the app start on the user's current location.
Could you please share the crash log? That will be helpful to narrow the root issue.
Also, if you haven't already, don't forget to request location permissions so you can use location services.

NavController no current navigation node after device rotation

I am making app, which supports different device orientations. Navigation is carried out by Android Jetpack's Navigation. App main screen for landscape orientation is present below. It is list wrapper fragment (it is NavHostFragment, it is added to activity's layout in fragment tag), wrapper includes list fragment (fragment) and details fragment (FrameLayout). Portrait orientation is similar (wrapper and list in it, details is accessible throw navigation).
My problem is after I change device orientation I get exception
java.lang.IllegalStateException: no current navigation node
First version of my layout with mocked data worked fine, the error appears after I add ROOM to my app, new order and update order fragments. It is a pity, I cannot localize source of error more exact.
List wrapper code
class OrderListWrapperFragment : RxFragment() {
private val disposable = CompositeDisposable()
var selectedOrderId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bundle = savedInstanceState ?: arguments
bundle?.let {
selectedOrderId = it.getLong(EXTRA_ORDER_ID)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.orders__list_wrapper, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initializeToolbar(toolbar, getString(R.string.orders_title), false)
newOrderButton
.clicks()
.subscribe {
findNavController()
.navigate(R.id.action_orderListWrapperFragment_to_orderNewFragment)
}
.addTo(disposable)
childFragmentManager.registerFragmentLifecycleCallbacks(callback, false)
}
override fun onDestroyView() {
super.onDestroyView()
childFragmentManager.unregisterFragmentLifecycleCallbacks(callback)
disposable.clear()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putLong(EXTRA_ORDER_ID, selectedOrderId)
}
private val callback = object : FragmentManager.FragmentLifecycleCallbacks() {
private val disposable = CompositeDisposable()
override fun onFragmentResumed(fm: FragmentManager, fragment: Fragment) {
super.onFragmentResumed(fm, fragment)
if (fragment is OrderListFragment) {
fragment
.selectedItemIdChanges
.subscribeBy(onNext = {
selectedOrderId = it
if (orderDetailsContainer != null) {
childFragmentManager.commit {
replace(
R.id.orderDetailsContainer,
OrderDetailsFragment.newInstance(it)
)
}
} else {
findNavController()
.navigate(
R.id.action_orderListWrapperFragment_to_orderDetailsFragment,
bundleOf(EXTRA_ORDER_ID to it)
)
selectedOrderId = 0
}
},
onError = {
Log.d("detailView", it.toString())
})
.addTo(disposable)
val orderId = selectedOrderId
if (orderId != 0L) {
if (orderDetailsContainer != null) {
childFragmentManager.commit {
replace(
R.id.orderDetailsContainer,
OrderDetailsFragment.newInstance(orderId)
)
}
} else {
findNavController()
.navigate(//exception throws here
R.id.action_orderListWrapperFragment_to_orderDetailsFragment,
bundleOf(EXTRA_ORDER_ID to orderId)
)
selectedOrderId = 0
}
}
}
}
override fun onFragmentPaused(fm: FragmentManager, fragment: Fragment) {
super.onFragmentPaused(fm, fragment)
if (fragment is OrderListFragment) {
disposable.clear()
}
}
}
companion object {
private const val EXTRA_ORDER_ID = "EXTRA_ORDER_ID"
}
}
My navigation graph
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="#+id/navigation_graph"
app:startDestination="#id/orderListWrapperFragment">
<fragment
android:id="#+id/orderListWrapperFragment"
android:name="com.mtgshipping.app.orders.orderList.OrderListWrapperFragment"
android:label="OrderListWrapperFragment"
tools:layout="#layout/orders__list_wrapper">
<action
android:id="#+id/action_orderListWrapperFragment_to_orderDetailsFragment"
app:destination="#id/orderDetailsFragment"/>
<action
android:id="#+id/action_orderListWrapperFragment_to_orderNewFragment"
app:destination="#id/orderNewFragment"/>
<action
android:id="#+id/action_orderListWrapperFragment_to_orderUpdateFragment"
app:destination="#id/orderUpdateFragment"/>
</fragment>
<fragment
android:id="#+id/orderDetailsFragment"
android:name="com.mtgshipping.app.orders.orderDetails.OrderDetailsFragment"
android:label="OrderDetailsFragment"
tools:layout="#layout/orders__order_details">
<action
android:id="#+id/action_orderDetailsFragment_to_orderUpdateFragment"
app:destination="#id/orderUpdateFragment"/>
</fragment>
<fragment
android:id="#+id/orderNewFragment"
android:name="com.mtgshipping.app.orders.orderNew.OrderNewFragment"
android:label="OrderNewFragment"
tools:layout="#layout/orders__order_new">
<action
android:id="#+id/action_orderNewFragment_to_orderListWrapperFragment"
app:destination="#id/orderListWrapperFragment"/>
</fragment>
<fragment
android:id="#+id/orderUpdateFragment"
android:name="com.mtgshipping.app.orders.orderUpdate.OrderUpdateFragment"
android:label="OrderUpdateFragment"
tools:layout="#layout/orders__order_update">
<action
android:id="#+id/action_orderUpdateFragment_to_orderListWrapperFragment"
app:destination="#id/orderListWrapperFragment"/>
</fragment>
</navigation>
I made some debug in NavController, it showed in line 746 NavDestination currentNode = mBackStack.isEmpty() ? mGraph : mBackStack.getLast().getDestination(); after device rotation mGraph is null, other private fields is also null. Maybe something prevents NavController to initialize properly. I can provide layouts and other code, if it will be need.
Thanks to Slav's comment, he was right. I updated navigation module to 2.2.0 navigation_version = '2.2.0' in app's module build.gradle
implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
After doing this problem is no longer appears, looks like that was a bug in navigation.
You can also fix it like this.
In your host activity in manifest adding this atribute:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
But the best way is change your dependencies for navigation from:
implementation "android.arch.navigation:navigation-fragment-ktx:$navigation_version"
implementation "android.arch.navigation:navigation-ui-ktx:$navigation_version"
to
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
The problem for me was the version of lifecycle dependency
"androidx.lifecycle:lifecycle-extensions:$lifecycle_version
version '2.2.0' cause the problem, I returned to use '2.1.0'
lifecycle_version = '2.1.0'
None of these solutions fixed my issue.
I fixed it by including my nested graph into the main graph.
I have a nav_main which includes a nav_sub_1.
nav_sub_1 also includes another sub graph, nav_sub_2
When I tried to start my activity by setting nav_sub_2, the IllegalStateException occured
java.lang.IllegalStateException: no current navigation node
But this would not happen by setting nav_main or nav_sub_1.
My main graph nav_main looks like this:
<navigation
<fragment...>
<include app:graph="#navigation/nav_sub_1
<navigation/>
and nav_sub_1:
<navigation
<fragment...>
<include app:graph="#navigation/nav_sub_2
<navigation/>
I included nav_sub_2 in nav_main and the issue was fixed!
<navigation
<fragment...>
<include app:graph="#navigation/nav_sub_1
<include app:graph="#navigation/nav_sub_2
<navigation/>
For me the fragment had the wrong parent. Changing this
class MyFragment: DynamicNavHostFragment() ...
to this
class MyFragment: Fragment() ...
Fixed that issue

How to fix "Unresolved reference" when passing context with this#MapsActivity

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

Data binding LiveData from Transformation - Android Kotlin

I'm learning kotlin and android architecture components.
I have a simple use case of a map toggle button on a google map.
I want to use data binding to bind the map toggle button label to a MutableLiveData field in my ViewModel.
I set the mapType val in the MapViewModel from the onCreate method in the Activity. If I understand correctly, this should trigger the mapLabel val to change due to the use of Transformations.map.
Its not working... Why?
Here's my versions:
Android studio 3.2 Canary 4
kotlin_version = '1.2.21'
support = "27.1.0"
arch_core = "1.1.0"
databinding = "3.2.0-alpha04"
MapViewModel.kt
class MapViewModel : ViewModel() {
val mapType: MutableLiveData<MapType> = MutableLiveData()
val mapLabel: LiveData<String> = Transformations.map(mapType, {
if (it == MapType.MAP) "SAT" else "MAP"
})
}
enum class MapType {
SAT, MAP
}
activity_maps.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="vm"
type="uk.co.oliverdelange.wcr_android_kt.ui.map.MapViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MapsActivity">
<Button
android:id="#+id/map_toggle"
style="#style/Wcr_MapToggle"
android:layout_marginTop="110dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="7dp"
android:layout_gravity="top|end"
android:text="#{vm.mapLabel}" />
</fragment>
</FrameLayout>
</layout>
MapsActivity.kt
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var viewModel: MapViewModel
private lateinit var binding: ActivityMapsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
viewModel = ViewModelProviders.of(this).get(MapViewModel::class.java)
binding.vm = viewModel
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// I can do it this way, but I don't want to.
// viewModel.mapLabel.observe(this, Observer { map_toggle.text = it })
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
}
}
I've tested the binding with a MutableLiveData object where i set the string in the activity, and it works fine. The problem seems to be with the Transformations.map - have i just understood it wrong?
Also, whilst debugging, i see that the mapType val in my ViewModel has no observers (not sure if this is right or wrong, just interesting)
The issue here was that despite being bound to the mapLabel field, the view binding wasn't being updated when the value of the mapLabel field changed.
The reason is that I didn't set the lifecycle owner on the binding.
binding.setLifecycleOwner(this)
I realised my mistake after reading this blog post for the 10th time.
My new MapsActivity.kt
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private lateinit var viewModel: MapViewModel
private lateinit var binding: ActivityMapsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_maps)
binding.setLifecycleOwner(this) //<- NEW!
viewModel = ViewModelProviders.of(this).get(MapViewModel::class.java)
binding.vm = viewModel
val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
}
}
On the plus side I learned a lot about how LiveData works internally!
Its not working... Why?
When you change the value of mapType, the value of mapLabel doesn't change immediately. You need to set value of mapLabel again. For ex, after you set value for mapType, you call method to update value of mapLabel
// Here is where i'm setting the MapType on the ViewModel.
viewModel.mapType.value = MapType.MAP
viewModel.updateMapLabel()
Your viewModel:
fun updateMapLabel() {
mapLabel.value =
if (it == MapType.MAP) "SAT" else "MAP"
}

Android Parcelable for Kotlin is not working

I am trying to pass data from one fragment to another but I am facing issue with sending data from parcelable from one fragment to another.
class MainFragment : Fragment() {
companion object {
public val KEY_PARSE_DATA = "parseData"
}
private var parseData: ParseData? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_main, container, false).apply {
val editName = findViewById(R.id.edit_Name) as EditText
val editSurname = findViewById(R.id.edit_Surname) as EditText
val buttonNext = findViewById(R.id.btn_Next) as Button
buttonNext.setOnClickListener(View.OnClickListener {
val fragment = AnotherFragment()
if (parseData != null) {
var parseData = ParseData(editName.text.toString(), editSurname.text.toString())
val fragment = AnotherFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, parseData)
fragment.setArguments(bundle)
fragmentManager.beginTransaction().add(R.id.fragment_container, fragment).commitAllowingStateLoss()
}
})
}
}
}// Required empty public constructor
Parcelable class for implementing it
#SuppressLint("ParcelCreator")
#Parcelize
data class ParseData(val firstName: String, val lastName: String) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString()) {
}
override fun toString(): String {
return "ParseData(firstName='$firstName', lastName='$lastName')"
}
companion object : Parceler<ParseData> {
override fun ParseData.write(parcel: Parcel, flags: Int) {
parcel.writeString(firstName)
parcel.writeString(lastName)
}
override fun create(parcel: Parcel): ParseData {
return ParseData(parcel)
}
}
}
And another fragment which grab data from parcelable class in android
class AnotherFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater!!.inflate(R.layout.fragment_another, container, false).apply {
val textName = findViewById<TextView>(R.id.textFirst)
val textSurname = findViewById<TextView>(R.id.textSecond)
val bundle = arguments
if (bundle != null) {
val parseData = bundle.getParcelable<ParseData>(KEY_PARSE_DATA)
textName.setText(parseData.firstName)
textSurname.setText(parseData.lastName)
}
}
}
}
I tried some example but I cant get clear idea how parcelable is implemented in andoid application based on kotlin and build.gradle file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlinsecond"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
androidExtensions {
experimental = true
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}
Add below code into your App Level gradle.
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
androidExtensions {
experimental = true
}
Now it is enough to add 'kotlin-parcelize' plugin.
So for example:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
//
androidExtensions {
experimental = true
}
}
The order of plugin matters. kotlin-android must be before kotlin-android-extension. Then clean project.
Unable to add kotlin android extension
I'd recommend taking a look at the #Parcelize annotation provided with the Kotlin Android Extensions, it generates all necessary boilerplate for you so you don't have to write and maintain that code yourself.
Need to add these two in your app level Gradle
apply plugin: 'kotlin-android-extensions'
androidExtensions {
experimental = true`
}
Kotlin plugin should be enabled before 'kotlin-android-extensions'
so change the order to
apply plugin: 'com.android.application' #1
apply plugin: 'kotlin-android' #2
apply plugin: 'kotlin-android-extensions' #3
Hello All I had solved this question and there was issue with fragment transaction and parcelable
MainActivity to setup fragment
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragment = DetailFragment()
manager.findFragmentById(R.id.fragmentContainer)
manager.beginTransaction().add(R.id.fragmentContainer, fragment).commit()
}
}
First fragment to fill data
class DetailFragment : Fragment(), View.OnClickListener {
var editTextName: EditText? = null
var editTextLast: EditText? = null
var buttonNextFragment: Button? = null
var firstName: String? = null
var lastName: String? = null
companion object {
val KEY_PARSE_DATA = "detailData"
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_detail, container, false)
editTextName = view.findViewById(R.id.ed_firstName) as EditText
editTextLast = view.findViewById(R.id.ed_lastName) as EditText
buttonNextFragment = view.findViewById(R.id.btn_nextfragment) as Button
buttonNextFragment!!.setOnClickListener(this)
return view
}
override fun onClick(v: View?) {
firstName = editTextName!!.text.toString()
lastName = editTextLast!!.text.toString()
Toast.makeText(context, firstName, Toast.LENGTH_SHORT).show()
// val viewFragment = ViewFragment()
// val transaction = fragmentManager.beginTransaction()
// transaction.replace(R.id.fragmentContainer, viewFragment)
// transaction.commit()
var details = Details(firstName!!, lastName!!)
val viewFragment = ViewFragment()
val bundle = Bundle()
bundle.putParcelable(KEY_PARSE_DATA, details)
viewFragment.setArguments(bundle)
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, viewFragment)
transaction.commit()
}
}
Display data coming from parcelable
class ViewFragment : Fragment() {
var textViewName: TextView? = null
var textViewLastName: TextView? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view: View = inflater!!.inflate(R.layout.fragment_view, container, false)
textViewName = view.findViewById(R.id.text_name_another) as TextView
textViewLastName = view.findViewById(R.id.text_surname_another) as TextView
val bundle = arguments
if (bundle != null) {
val details = bundle.getParcelable<Details>(KEY_PARSE_DATA)
textViewName!!.setText(details.firstName)
textViewLastName!!.setText(details.lastName)
}
return view
}
}
Model class for implementation of parcelable
#Parcelize
class Details(val firstName: String, val lastName: String) : Parcelable
and my gradle file
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.assignment.ankitt.kotlintwo"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
androidExtensions {
experimental = true
}
}

Categories

Resources