I put a Map Fragment inside dashboard fragment shown in image below:
To view the image clearly https://i.stack.imgur.com/NcxLB.jpg
whenever i am on home fragment and click on dashboard fragment it takes 3-4 sec (at the first time) and 1-2 sec
here is the code of dashboard fragment
class DashboardFragment :
BaseFragment<DashboardViewModel, FragmentDashboardBinding, WeatherRepository>(),
GoogleMap.OnMapClickListener,
GoogleMap.OnMapLongClickListener,
GoogleMap.OnCameraIdleListener,
OnMapReadyCallback {
private var map: GoogleMap? = null
private var cameraPosition: CameraPosition? = null
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val defaultLocation = LatLng(-33.8523341, 151.2106085)
private var locationPermissionGranted = false
private var lastKnownLocation: Location? = null
private var weatherData: WeatherData? = null
private lateinit var bottomSheetBehavior: BottomSheetBehavior<NestedScrollView>
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (savedInstanceState != null) {
lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION)
weatherData = savedInstanceState.getParcelable(KEY_WEATHER_DATA)
}
Log.e("Weather Data", weatherData.toString())
fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(requireActivity())
val mapFragment =
childFragmentManager.findFragmentById(R.id.myMap) as SupportMapFragment?
mapFragment?.getMapAsync(this#DashboardFragment)
bottomSheetBehavior = BottomSheetBehavior.from(binding.root.bottomSheet)
bottomSheetBehavior.addBottomSheetCallback(object :
BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(bottomSheet: View, slideOffset: Float) { /*handle onSlide*/ }
override fun onStateChanged(bottomSheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_COLLAPSED -> {
binding.root.bottomSheet.background =
(ContextCompat.getDrawable(requireContext(), R.drawable.round))
}
BottomSheetBehavior.STATE_EXPANDED -> {
binding.root.bottomSheet.background =
(ContextCompat.getDrawable(requireContext(), R.drawable.edge))
}
BottomSheetBehavior.STATE_DRAGGING -> {
}
BottomSheetBehavior.STATE_SETTLING -> {
}
BottomSheetBehavior.STATE_HIDDEN -> {
}
else -> {
}
}
}
})
viewModel.weatherResponse.observe(viewLifecycleOwner, {
when (it) {
is Resource.Success -> {
updateUi(binding.root, it.value)
weatherData = it.value
}
is Resource.Failure -> {
Toast.makeText(
requireContext(),
"Damn, Failed To Load Data",
Toast.LENGTH_SHORT
).show()
}
}
})
}
override fun onSaveInstanceState(outState: Bundle) {
map?.let { map ->
outState.putParcelable(KEY_CAMERA_POSITION, map.cameraPosition)
outState.putParcelable(KEY_LOCATION, lastKnownLocation)
}
super.onSaveInstanceState(outState)
}
override fun onMapReady(map: GoogleMap) {
this.map = map
map.setOnMapClickListener(this)
map.setOnMapLongClickListener(this)
map.setOnCameraIdleListener(this)
getLocationPermission()
updateLocationUI()
getDeviceLocation()
}
var marker: Marker? = null
override fun onMapClick(point: LatLng) {
if (marker == null){
marker = map?.addMarker(
MarkerOptions()
.position(point)
.title("${point.latitude.toString()},${point.longitude.toString()} is the location")
)
} else {
marker!!.position = point
marker!!.title = "${point.latitude.toString()},${point.longitude.toString()} is the location"
}
viewModel.getWeather(
point.latitude.toString(),
point.longitude.toString(),
app_id
)
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED)
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
else
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
Toast.makeText(this.context, "click $point", Toast.LENGTH_SHORT).show()
}
override fun onMapLongClick(point: LatLng) {
try {
val manager: FragmentManager =
(this.context as AppCompatActivity).supportFragmentManager
CustomBottomSheetDialogFragment().show(manager, CustomBottomSheetDialogFragment.TAG)
} catch (e: Exception) {
Log.e("❤", e.printStackTrace().toString())
}
Toast.makeText(this.context, "long click", Toast.LENGTH_SHORT).show()
}
override fun onCameraIdle() {
//if(!::map.isInitialized) return
//cameraTextView.text = map.cameraPosition.toString()
//Toast.makeText(this.context, "camera idle", Toast.LENGTH_SHORT).show()
}
private fun updateLocationUI() {
if (map == null) {
return
}
try {
if (locationPermissionGranted) {
map?.isMyLocationEnabled = true
map?.uiSettings?.isMyLocationButtonEnabled = true
} else {
map?.isMyLocationEnabled = false
map?.uiSettings?.isMyLocationButtonEnabled = false
lastKnownLocation = null
getLocationPermission()
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
private fun getDeviceLocation() {
try {
if (locationPermissionGranted) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener { task ->
if (task.isSuccessful) {
lastKnownLocation = task.result
if (lastKnownLocation != null) {
map?.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude
), DEFAULT_ZOOM.toFloat()
)
)
viewModel.getWeather(
lastKnownLocation!!.latitude.toString(),
lastKnownLocation!!.longitude.toString(),
app_id
)
}
} else {
Log.d(TAG, "Current location is null. Using defaults.")
Log.e(TAG, "Exception: %s", task.exception)
map?.moveCamera(
CameraUpdateFactory
.newLatLngZoom(defaultLocation, DEFAULT_ZOOM.toFloat())
)
map?.uiSettings?.isMyLocationButtonEnabled = false
}
}
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
Toast.makeText(this.context, "Some exception occurred", Toast.LENGTH_SHORT).show()
}
}
private fun getLocationPermission() {
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
} else {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
locationPermissionGranted = false
when (requestCode) {
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
}
}
}
updateLocationUI()
}
override fun getViewModal() = DashboardViewModel::class.java
override fun getFragmentBinding(
inflater: LayoutInflater,
container: ViewGroup?
) = FragmentDashboardBinding.inflate(inflater, container, false)
override fun getFragmentRepository() =
WeatherRepository(remoteDataSource.buildApi(WeatherApi::class.java))
companion object {
private val TAG = DashboardFragment::class.java.simpleName
private const val DEFAULT_ZOOM = 15
private const val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1
private const val KEY_CAMERA_POSITION = "camera_position"
private const val KEY_LOCATION = "location"
private const val KEY_WEATHER_DATA = "weather_data"
const val app_id = "*******api********"
}
}
even if i comment the whole code it the onActivityCreated() method still the lag occurs.
I used the code from the official Google Maps Platform Documentation, still the example app they use don't lags like my app.
Any help will be appreciated.
Related
I am developing location based android app using google maps but when I run project I am getting crash and getting following exception java.lang.IllegalStateException: Fragment MapFragment{2fe7116} (69b05d78-5cf3-46a9-829c-6b6507944575) not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:995)
I am getting exception in following function in my MapFragment
#SuppressLint("MissingPermission")
private fun getDeviceLocation() {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (locationPermissionGranted) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener(requireActivity()) { task ->
if (task.isSuccessful) {
// Set the map's camera position to the current location of the device.
lastKnownLocation = task.result
if (lastKnownLocation != null) {
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude
), DEFAULT_ZOOM.toFloat()
)
)
} else {
gotoDefaultLocation()
}
} else {
Log.d(TAG, "Current location is null. Using defaults.")
Log.e(TAG, "Exception: %s", task.exception)
gotoDefaultLocation()
}
}
}
} catch (e: SecurityException) {
MakeToast("Standort Zugriff leider nicht erlaubt")
Log.e("Exception: %s", e.message, e)
}
}
I have followed all stackoverflow answer it did not solve my problem
I have tried following stackoverflow answer as well java.lang.IllegalStateException: Fragment not attached to Activity I want to know where exactly I am making mistake
below My Full MapFragment.kt
#AndroidEntryPoint
class MapFragment : Fragment(), OnMapReadyCallback, GoogleMap.OnMarkerClickListener,
GoogleMap.OnInfoWindowClickListener {
private val binding by lazy { MapFragmentBinding.inflate(layoutInflater) }
private var locationPermissionGranted: Boolean = false
// private var mMap: MapView? = null
private lateinit var map: GoogleMap
private val cafMarker: MutableList<CafMarker> = mutableListOf()
private val viewModel by viewModels<MapViewModel>()
#Inject
lateinit var sharedPref: SharedPref
// Declare a variable for the cluster manager.
private lateinit var clusterManager: ClusterManager<CafMarker>
private val markerAdapter by lazy { MarkerAdapter() }
private lateinit var dialogMarker: Dialog
private var isRegistered: Boolean = false
// The entry point to the Places API.
private lateinit var placesClient: PlacesClient
private var lastKnownLocation: Location? = null
private var cameraPosition: CameraPosition? = null
// The entry point to the Fused Location Provider.
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val defaultLocation = LatLng(51.1633611, 10.4476833)
lateinit var CurrentChargePoint: ChargePoint
lateinit var cp: ChargePoint
private var ChargePoints: MutableList<ChargePoint> = mutableListOf<ChargePoint>()
private val TAG = "MapFragment"
private lateinit var activityContext: Context
companion object {
fun newInstance() = MapFragment()
val REQUEST_CODE_LOCATION = 32
private const val COUNTRY_ZOOM = 5.85
private const val DEFAULT_ZOOM = 15
private const val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1
private const val KEY_CAMERA_POSITION = "camera_position"
private const val KEY_LOCATION = "location"
private const val M_MAX_ENTRIES = 5
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
Log.d("setNavigationToken", "setNavigation: ${sharedPref.token}")
activityContext = requireContext()
binding.mapView.onCreate(savedInstanceState)
binding.mapView.getMapAsync(this)
binding.mapView.setOnClickListener(clickListener)
binding.gotoBookingButton.setOnClickListener(clickListener);
binding.StationInfo.isVisible = false;
createChannel("caf", "alarm")
if (savedInstanceState != null) {
lastKnownLocation = savedInstanceState.getParcelable(KEY_LOCATION)
cameraPosition = savedInstanceState.getParcelable(KEY_CAMERA_POSITION)
}
binding.qrButton.setOnClickListener(clickListener)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.map(sharedPref.token)
Log.d("onViewCreatedToken", "onViewCreated: ${sharedPref.token}")
}
val clickListener = View.OnClickListener { view ->
when (view.getId()) {
// R.id.gotoBookingButton -> {
// appCommon.CurrentChargePoint = CurrentChargePoint
// if (sharedPref.token.length > 0) {
// Log.d("#######", ": $sharedPref")
//// appCommon.isAdhocBooking = false
// findNavController().navigate(R.id.navigation_reserve)
// } else {
// findNavController().navigate(R.id.navigation_settings_login)
// }
// }
R.id.mapView -> {
binding.StationInfo.isVisible = false
}
R.id.qrButton -> {
findNavController().navigate(R.id.activity_qr)
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
binding.mapView.onSaveInstanceState(outState)
}
#SuppressLint("MissingPermission")
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
gotoDefaultLocation()
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE_LOCATION
)
return
}
locationPermissionGranted = true
map.isMyLocationEnabled = true;
//gMap.addMarker(MarkerOptions().position(devDefault).title("Hier bin ich"))
updateLocationUI()
getDeviceLocation()
map.setOnMarkerClickListener(this)
getChargePoints()
// FetchChargePoints()
}
val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
when (intent?.action) {
SET_MAP_PERMISSIONS -> {
if (intent.extras?.get("result")?.toString()!!
.toInt() == PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
updateLocationUI()
getDeviceLocation()
getChargePoints()
}
}
// BROADCAST_CHANGE_TYPE_CHANGED -> handleChangeTypeChanged()
}
}
}
#SuppressLint("MissingPermission")
private fun updateLocationUI() {
if (map == null) {
return
}
try {
if (locationPermissionGranted) {
map.isMyLocationEnabled = true
map.uiSettings.isMyLocationButtonEnabled = true
} else {
map.isMyLocationEnabled = false
map.uiSettings.isMyLocationButtonEnabled = false
lastKnownLocation = null
getLocationPermission()
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
private fun getLocationPermission() {
/*
* Request location permission, so that we can get the location of the
* device. The result of the permission request is handled by a callback,
* onRequestPermissionsResult.
*/
if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
updateLocationUI()
} else {
ActivityCompat.requestPermissions(
requireActivity(), arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
)
}
}
#SuppressLint("MissingPermission")
private fun getDeviceLocation() {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (locationPermissionGranted) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener(requireActivity()) { task ->
if (task.isSuccessful) {
// Set the map's camera position to the current location of the device.
lastKnownLocation = task.result
if (lastKnownLocation != null) {
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude
), DEFAULT_ZOOM.toFloat()
)
)
} else {
gotoDefaultLocation()
}
} else {
Log.d(TAG, "Current location is null. Using defaults.")
Log.e(TAG, "Exception: %s", task.exception)
gotoDefaultLocation()
}
}
}
} catch (e: SecurityException) {
MakeToast("Standort Zugriff leider nicht erlaubt")
Log.e("Exception: %s", e.message, e)
}
}
private fun gotoDefaultLocation() {
map.moveCamera(
CameraUpdateFactory
.newLatLngZoom(defaultLocation, COUNTRY_ZOOM.toFloat())
)
map.uiSettings.isMyLocationButtonEnabled = false
}
private fun MakeToast(text: String) {
val toast = text
Toast.makeText(
activity?.applicationContext,
toast,
Toast.LENGTH_SHORT
).show()
}
override fun onResume() {
super.onResume()
registerReceiver()
binding.mapView.onResume()
}
override fun onPause() {
super.onPause()
binding.mapView.onPause()
}
override fun onStart() {
super.onStart()
registerReceiver()
binding.mapView.onStart()
}
override fun onStop() {
super.onStop()
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(broadCastReceiver)
binding.mapView.onStop()
}
override fun onDestroy() {
super.onDestroy()
// binding.mapView.onDestroy()
}
private fun registerReceiver() {
if (!isRegistered) {
val filter = IntentFilter()
filter.addAction(SET_MAP_PERMISSIONS)
// filter.addAction(END_PAYMENT)
LocalBroadcastManager.getInstance(requireContext())
.registerReceiver(broadCastReceiver, filter)
isRegistered = true
}
}
override fun onMarkerClick(p0: Marker): Boolean {
getCPs(p0)
UseCpInfo()
return true
}
private fun UseCpInfo() {
if (this::cp.isInitialized) {
CurrentChargePoint = cp // it means
// binding.StationInfo.isVisible = true
// val streetName = binding.StationInfo.findViewById(R.id.textViewStreet) as TextView
// val stationCity = binding.StationInfo.findViewById(R.id.textViewCity) as TextView
// val stationName = binding.StationInfo.findViewById(R.id.textViewStationName) as TextView
// stationName.text = cp.getCpLocName()
// streetName.text = cp.getStreetName()
// stationCity.text = cp.getCityName()
// Log.d("MapFragmentAdhok", "UseCpInfo: ${cp.hasType(ChargePointType.AdHoc)}")
// if (cp.hasType(ChargePointType.AdHoc)) {
// binding.textViewAvailability.text = "nur Direktladen, " + cp.Tarif + " Ct/kWh"
// } else if (cp.Available) {
// binding.textViewAvailability.text =
// String.format(Locale.GERMAN, "%.0f", cp.Tarif) + " Ct/kWh - " + String.format(
// Locale.GERMAN,
// "%.1f",
// cp.Power
// ) + " kW"
// } else {
// binding.textViewAvailability.text = "belegt, " + String.format(
// Locale.GERMAN,
// "%.0f",
// cp.Tarif
// ) + " Ct/kWh - " + String.format(Locale.GERMAN, "%.1f", cp.Power) + " kW"
// }
} else {
MakeToast("Station leider nicht verfügbar")
}
}
private fun getCPs(p0: Marker) {
for (i in 0 until ChargePoints.size) {
if (ChargePoints[i].id == p0.id) {
cp = ChargePoints[i]
}
}
}
private fun getCPsByCafMarker(p0: CafMarker) {
for (i in 0 until ChargePoints.size) {
if (ChargePoints[i].Guid == p0.GetId()) {
cp = ChargePoints[i]
}
}
}
private fun getChargePoints() {
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.map.collect {
when (it) {
is UiStateList.LOADING -> {
}
is UiStateList.SUCCESS -> {
Log.d("MapFragmentCharge", "getChargePoint: ${it.data}")
appCommon.setChargePoints(it.data)
setUpClusterer()
}
is UiStateList.ERROR -> {
Log.d(TAG, "getChargePoints: ${it.message}")
Toast.makeText(requireContext(), it.message, Toast.LENGTH_SHORT).show()
}
}
}
}
}
private fun setUpClusterer() {
val markersList = ArrayList<CafMarker>()
clusterManager = ClusterManager<CafMarker>(activityContext, map)
clusterManager.renderer = MapRenderer(activityContext, map, clusterManager)
Log.d(TAG, "setUpClusterer: ${clusterManager.markerCollection}")
map.setOnCameraIdleListener(clusterManager)
map.setOnMarkerClickListener(clusterManager)
map.setOnInfoWindowClickListener(clusterManager)
clusterManager.setOnClusterItemClickListener {
getCPsByCafMarker(it)
UseCpInfo()
CurrentChargePoint = cp
appCommon.CurrentChargePoint = CurrentChargePoint
appCommon.isAdhocBooking = CurrentChargePoint.hasType(ChargePointType.AdHoc)
if (!appCommon.CurrentChargePoint.hasType(ChargePointType.Manual) and !appCommon.CurrentChargePoint.hasType(ChargePointType.Calibrated)) {
Log.d("updateAccessProfileUIReserve", "addItems: ")
appCommon.CurrentChargePoint.hasType(ChargePointType.Calibrated)
}
// appCommon.isCalibrated = CurrentChargePoint.hasType(ChargePointType.Calibrated)
Log.d("TAGsetUpClusterer", "setUpClusterer: $cp")
Log.d(TAG, "setUpClusterer: $CurrentChargePoint")
if (sharedPref.token.length > 0) {
Log.d("#######", ": $sharedPref")
// appCommon.isAdhocBooking = false
findNavController().navigate(R.id.navigation_reserve)
} else {
findNavController().navigate(R.id.navigation_settings_login)
}
true
}
clusterManager.setOnClusterClickListener { cluster ->
// Add ten cluster items in close proximity, for purposes of this example.
val items: List<ChargePoint> = appCommon.getChargePoints()
var available = ""
var tarif = ""
var kW = ""
for (i in cluster.items) {
for (k in 0 until items.size) {
val cp: ChargePoint = items[k]
if (cp.Guid == i.GetId()) {
if (cp.hasType(ChargePointType.AdHoc) == false) {
Log.d("ChargePointBall", "Tarif: ${cp.Tarif}")
available =
requireContext().getResources().getString(R.string.available) + ":"
tarif = cp.Tarif.toInt().toString() + " Ct/kWh"
kW = cp.Power.toString() + " kW"
} else {
available = requireContext().getResources()
.getString(R.string.not_reservable) + ":"
tarif = cp.Tarif.toInt().toString() + " Ct/kWh"
kW = cp.Power.toString() + " kW"
}
}
}
val markerModel = CafMarker(
i.position.latitude,
i.position.longitude,
i.GetId(),
i.title,
i.snippet,
null,
available,
tarif,
kW
)
markersList.add(markerModel)
}
markerAdapter.submitData(markersList)
if (markersList.size > 0) {
showProfessionsDialog()
markersList.clear()
markerAdapter.itemClickListener = {
getCPsByCafMarker(it)
UseCpInfo()
CurrentChargePoint = cp
appCommon.CurrentChargePoint = CurrentChargePoint
Log.d("TAGsetUpClusterer", "setUpClusterer: $cp")
Log.d(TAG, "setUpClusterer: $CurrentChargePoint")
dialogMarker.dismiss()
if (sharedPref.token.length > 0) {
Log.d("#######", ": $sharedPref")
// appCommon.isAdhocBooking = false
findNavController().navigate(R.id.navigation_reserve)
} else {
findNavController().navigate(R.id.navigation_settings_login)
}
map.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
it.position.latitude, it.position.longitude
), DEFAULT_ZOOM.toFloat()
)
)
}
}
Log.d(TAG, "setUpClusterer: clicked")
map.animateCamera(
CameraUpdateFactory.newLatLngZoom(
cluster.getPosition(),
Math.floor((map.cameraPosition.zoom + 1).toDouble()).toFloat()
), 300, null
)
true
}
addItems()
getDeviceLocation()
}
private fun showProfessionsDialog() {
dialogMarker = Dialog(requireContext())
val binding = DialogMarkersBinding.inflate(layoutInflater)
dialogMarker.setContentView(binding.root)
dialogMarker.setCancelable(true)
dialogMarker.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
binding.rvMarkers.adapter = markerAdapter
dialogMarker.show()
}
override fun onInfoWindowClick(marker: Marker) {
onMarkerClick(marker);
}
private fun addItems() {
// Add ten cluster items in close proximity, for purposes of this example.
val items: List<ChargePoint> = appCommon.getChargePoints()
var isGood: Boolean = true
for (i in 0 until items.size) {
val cp: ChargePoint = items[i]
if (cp.Guid != null) {
Log.d("Available", "addItems: ${cp.hasType(ChargePointType.AdHoc)}")
var color: MarkerColor = MarkerColor.Green
if (cp.hasType(ChargePointType.AdHoc)) {
color = MarkerColor.Blue
}
else if (!cp.Available) {
color = MarkerColor.Red
}
val offsetItem = CafMarker(cp.Lat, cp.Lon, cp.Guid, cp.CpName, cp.Name, color, "", "", "")
clusterManager.addItem(offsetItem)
val defaultClusterRenderer = clusterManager.renderer as DefaultClusterRenderer
// defaultClusterRenderer.minClusterSize = 4
if (defaultClusterRenderer.minClusterSize >= 6) {
clusterManager.addItem(offsetItem)
}
cp.uuid = offsetItem.GetId()
ChargePoints.add(i, cp)
} else {
isGood = false
}
}
if (!isGood) {
}
}
private fun createChannel(channelId: String, channelName: String) {
// TODO: Step 1.6 START create a channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Create channel to show notifications.
val notificationChannel = NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_HIGH
)
.apply {
setShowBadge(false)
}
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.RED
notificationChannel.enableVibration(true)
notificationChannel.description = getString(R.string.notification_channel_description)
val notificationManager = requireActivity().getSystemService(
NotificationManager::class.java
)
notificationManager.createNotificationChannel(notificationChannel)
}
}
}
Remove requireActivity(), it gives this IllegalStateException. See Explanation from official document.
locationResult.addOnCompleteListener {task ->
if (task.isSuccessful) {
// Your Code
}
}
Get Location, according to official documentation.
I just noticed problem earlier in my app, I see the ViewModel inside fragment doesn't save/keep recycler view when I rotate the device, I don't want to use the old method like save data in bundle onSaveInstanceState and restore it, I tried to figure why this problem by printing some logs on each method in fragment lifecycle but I didn't succeed
GIF showing the problem
the ViewModel
#HiltViewModel
class PostViewModel #Inject constructor(
private val mainRepository: MainRepository,
private val dataStoreRepository: DataStoreRepository,
application: Application
) :
AndroidViewModel(application) {
/** ROOM DATABASE */
val readAllPosts: LiveData<List<Item>> = mainRepository.localDataSource.getAllItems().asLiveData()
val postsBySearchInDB: MutableLiveData<List<Item>> = MutableLiveData()
/** RETROFIT **/
var postsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var searchedPostsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var postListResponse: PostList? = null
var postListByLabelResponse: PostList? = null
var searchPostListResponse: PostList? = null
val label = MutableLiveData<String>()
var finalURL: MutableLiveData<String?> = MutableLiveData()
val token = MutableLiveData<String?>()
val currentDestination = MutableLiveData<Int>()
fun getCurrentDestination() {
viewModelScope.launch {
dataStoreRepository.readCurrentDestination.collect {
currentDestination.value = it
}
}
}
val errorCode = MutableLiveData<Int>()
val searchError = MutableLiveData<Boolean>()
var networkStats = false
var backOnline = false
val recyclerViewLayout = dataStoreRepository.readRecyclerViewLayout.asLiveData()
val readBackOnline = dataStoreRepository.readBackOnline.asLiveData()
override fun onCleared() {
super.onCleared()
finalURL.value = null
token.value = null
}
private fun saveBackOnline(backOnline: Boolean) = viewModelScope.launch {
dataStoreRepository.saveBackOnline(backOnline)
}
fun saveCurrentDestination(currentDestination: Int) {
viewModelScope.launch {
dataStoreRepository.saveCurrentDestination(currentDestination)
}
}
fun saveRecyclerViewLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLayout(layout)
}
}
fun getPosts() = viewModelScope.launch {
getPostsSafeCall()
}
fun getPostListByLabel() = viewModelScope.launch {
getPostsByLabelSafeCall()
}
fun getItemsBySearch() = viewModelScope.launch {
getItemsBySearchSafeCall()
}
private suspend fun getPostsByLabelSafeCall() {
postsResponse.value = NetworkResult.Loading()
if (hasInternetConnection()) {
try {
val response = mainRepository.remoteDataSource.getPostListByLabel(finalURL.value!!)
postsResponse.value = handlePostsByLabelResponse(response)
} catch (ex: HttpException) {
Log.e(TAG, ex.message + ex.cause)
postsResponse.value = NetworkResult.Error(ex.message.toString())
errorCode.value = ex.code()
} catch (ex: NullPointerException) {
postsResponse.value = NetworkResult.Error("There's no items")
}
} else {
postsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private suspend fun getPostsSafeCall() {
postsResponse.value = NetworkResult.Loading()
if (hasInternetConnection()) {
try {
if (finalURL.value.isNullOrEmpty()) {
finalURL.value = "$BASE_URL?key=$API_KEY"
}
val response = mainRepository.remoteDataSource.getPostList(finalURL.value!!)
postsResponse.value = handlePostsResponse(response)
} catch (e: Exception) {
postsResponse.value = NetworkResult.Error(e.message.toString())
if (e is HttpException) {
errorCode.value = e.code()
Log.e(TAG, "getPostsSafeCall: errorCode $errorCode")
Log.e(TAG, "getPostsSafeCall: ${e.message.toString()}")
}
}
} else {
postsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private fun handlePostsResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
if (!(token.value.equals(response.body()?.nextPageToken))) {
token.value = response.body()?.nextPageToken
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.value = "${BASE_URL}?pageToken=${token.value}&key=${API_KEY}"
Log.e(TAG, "handlePostsResponse finalURL is ${finalURL.value!!}")
for (item in resultResponse.items) {
insertItem(item)
}
return NetworkResult.Success(resultResponse)
}
}
}
if (token.value == null) {
errorCode.value = 400
} else {
errorCode.value = response.code()
}
return NetworkResult.Error(
"network results of handlePostsResponse ${
response.body().toString()
}"
)
}
private fun handlePostsByLabelResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsByLabelResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.postValue(
(BASE_URL_POSTS_BY_LABEL + "posts?labels=${label.value}"
+ "&maxResults=20"
+ "&pageToken=")
+ token.value
+ "&key=" + API_KEY
)
if (postListByLabelResponse == null) {
postListByLabelResponse = resultResponse
} else {
val oldPosts = postListByLabelResponse?.items
val newPosts = resultResponse.items
oldPosts?.addAll(newPosts)
}
return NetworkResult.Success(postListByLabelResponse?:resultResponse)
}
}
if (token.value == null) {
errorCode.value = 400
} else {
errorCode.value = response.code()
}
Log.e(TAG, "handlePostsByLabelResponse: final URL ${finalURL.value}")
return NetworkResult.Error(
"network results of handlePostsByLabelResponse ${
response.body().toString()
}"
)
}
private fun hasInternetConnection(): Boolean {
val connectivityManager = getApplication<Application>().getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val capabilities =
connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
return when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true
else -> false
}
} else {
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnectedOrConnecting
}
}
fun showNetworkStats() {
if (!networkStats) {
Toast.makeText(getApplication(), "No Internet connection", Toast.LENGTH_SHORT).show()
saveBackOnline(true)
} else if (networkStats) {
if (backOnline) {
Toast.makeText(getApplication(), "We're back online", Toast.LENGTH_SHORT).show()
saveBackOnline(false)
}
}
}
private fun insertItem(item: Item) {
viewModelScope.launch(Dispatchers.IO) {
mainRepository.localDataSource.insertItem(item)
}
}
private suspend fun getItemsBySearchSafeCall() {
searchedPostsResponse.value = NetworkResult.Loading()
if (!label.value.isNullOrEmpty()) {
finalURL.value = "${BASE_URL}?labels=${label.value}&maxResults=500&key=$API_KEY"
}
Log.e(TAG, "getItemsBySearch: ${finalURL.value}")
if (hasInternetConnection()) {
try {
val response = mainRepository.remoteDataSource.getPostListBySearch(finalURL.value!!)
searchedPostsResponse.value = handlePostsBySearchResponse(response)
} catch (e: Exception) {
searchedPostsResponse.value = NetworkResult.Error(e.message.toString())
}
} else {
searchedPostsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private fun handlePostsBySearchResponse(response: Response<PostList>): NetworkResult<PostList> {
return if (response.isSuccessful) {
val postListResponse = response.body()
NetworkResult.Success(postListResponse!!)
} else {
errorCode.value = response.code()
NetworkResult.Error(response.errorBody().toString())
}
}
fun getItemsBySearchInDB(keyword: String) {
Log.d(TAG, "getItemsBySearchInDB: called")
viewModelScope.launch {
val items = mainRepository.localDataSource.getItemsBySearch(keyword)
if (items.isNotEmpty()) {
postsBySearchInDB.value = items
} else {
searchError.value = true
Log.e(TAG, "list is empty")
}
}
}
}
the fragment
#AndroidEntryPoint
class AccessoryFragment : Fragment(), MenuProvider, TitleAndGridLayout {
private var _binding: FragmentAccessoryBinding? = null
private val binding get() = _binding!!
private var searchItemList = arrayListOf<Item>()
private lateinit var postViewModel: PostViewModel
private val titleLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 2) }
private val gridLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 3) }
private var linearLayoutManager: LinearLayoutManager? = null
private val KEY_RECYCLER_STATE = "recycler_state"
private val mBundleRecyclerViewState by lazy { Bundle() }
private lateinit var adapter: PostAdapter
private var isScrolling = false
var currentItems = 0
var totalItems: Int = 0
var scrollOutItems: Int = 0
private var postsAPiFlag = false
private var keyword: String? = null
private lateinit var networkListener: NetworkListener
private var networkStats = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postViewModel = ViewModelProvider(this)[PostViewModel::class.java]
adapter = PostAdapter(this)
postViewModel.finalURL.value =
BASE_URL_POSTS_BY_LABEL + "posts?labels=Accessory&maxResults=20&key=$API_KEY"
networkListener = NetworkListener()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAccessoryBinding.inflate(inflater, container, false)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)
postViewModel.label.value = "Accessory"
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "onViewCreated: called")
postViewModel.recyclerViewLayout.observe(viewLifecycleOwner) { layout ->
linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
Log.w(TAG, "onViewCreated getSavedLayout called")
when (layout) {
"cardLayout" -> {
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 0
binding.accessoryRecyclerView.adapter = adapter
}
"cardMagazineLayout" -> {
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.accessoryRecyclerView.adapter = adapter
}
"titleLayout" -> {
binding.accessoryRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.accessoryRecyclerView.adapter = adapter
}
"gridLayout" -> {
binding.accessoryRecyclerView.layoutManager = gridLayoutManager
adapter.viewType = 3
binding.accessoryRecyclerView.adapter = adapter
}
}
}
lifecycleScope.launchWhenStarted {
networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
Log.d(TAG, "networkListener: $stats")
postViewModel.networkStats = stats
postViewModel.showNetworkStats()
this#AccessoryFragment.networkStats = stats
if (stats ) {
if (binding.accessoryRecyclerView.visibility == View.GONE) {
binding.accessoryRecyclerView.visibility = View.VISIBLE
}
requestApiData()
} else {
// Log.d(TAG, "onViewCreated: savedInstanceState $savedInstanceState")
noInternetConnectionLayout()
}
}
}
binding.accessoryRecyclerView.onItemClick { _, position, _ ->
val postItem = adapter.differ.currentList[position]
findNavController().navigate(
AccessoryFragmentDirections.actionNavAccessoryToDetailsActivity(
postItem
)
)
}
binding.accessoryRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
currentItems = linearLayoutManager!!.childCount
totalItems = adapter.itemCount
scrollOutItems = linearLayoutManager!!.findFirstVisibleItemPosition()
if ((!recyclerView.canScrollVertically(1) && dy > 0) &&
(isScrolling && currentItems + scrollOutItems >= totalItems && postsAPiFlag)
) {
hideShimmerEffect()
postViewModel.getPostListByLabel()
isScrolling = false
}
}
})
postViewModel.errorCode.observe(viewLifecycleOwner) { errorCode ->
if (errorCode == 400) {
binding.accessoryRecyclerView.setPadding(0, 0, 0, 0)
Toast.makeText(requireContext(), R.string.lastPost, Toast.LENGTH_LONG).show()
binding.progressBar.visibility = View.GONE
} else {
Log.e(TAG, "onViewCreated: ${postViewModel.errorCode.value.toString()} ")
noInternetConnectionLayout()
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.d(TAG, "onConfigurationChanged: ${newConfig.orientation}")
Log.d(TAG, "onConfigurationChanged: ${adapter.differ.currentList.toString()}")
Log.d(
TAG,
"onConfigurationChanged: " +
binding.accessoryRecyclerView.layoutManager?.itemCount.toString()
)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
Log.d(TAG, "onViewStateRestored: called")
}
private fun requestApiData() {
showShimmerEffect()
Log.d(TAG, "requestApiData: called")
postViewModel.getPostListByLabel()
postViewModel.postsResponse.observe(viewLifecycleOwner) { response ->
postsAPiFlag = true
when (response) {
is NetworkResult.Success -> {
hideShimmerEffect()
response.data?.let {
binding.progressBar.visibility = View.GONE
// itemArrayList.addAll(it.items)
adapter.differ.submitList(it.items.toList())
}
}
is NetworkResult.Error -> {
hideShimmerEffect()
binding.progressBar.visibility = View.GONE
Log.e(TAG, response.data.toString())
Log.e(TAG, response.message.toString())
}
is NetworkResult.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
}
}
}
private fun showShimmerEffect() {
binding.apply {
shimmerLayout.visibility = View.VISIBLE
accessoryRecyclerView.visibility = View.INVISIBLE
}
}
private fun hideShimmerEffect() {
binding.apply {
shimmerLayout.stopShimmer()
shimmerLayout.visibility = View.GONE
accessoryRecyclerView.visibility = View.VISIBLE
}
}
private fun changeAndSaveLayout() {
Log.w(TAG, "changeAndSaveLayout: called")
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
val recyclerViewLayouts = resources.getStringArray(R.array.RecyclerViewLayouts)
// SharedPreferences.Editor editor = sharedPreferences.edit();
builder.setItems(
recyclerViewLayouts
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardMagazineLayout")
}
2 -> {
adapter.viewType = 2
binding.accessoryRecyclerView.layoutManager = titleLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("titleLayout")
}
3 -> {
adapter.viewType = 3
binding.accessoryRecyclerView.layoutManager = gridLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("gridLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
val alertDialog = builder.create()
alertDialog.show()
}
private fun noInternetConnectionLayout() {
binding.apply {
// accessoryRecyclerView.removeAllViews()
Log.d(TAG, "noInternetConnectionLayout: called")
shimmerLayout.stopShimmer()
shimmerLayout.visibility = View.GONE
accessoryRecyclerView.visibility = View.GONE
}
binding.noInternetConnectionLayout.inflate()
binding.noInternetConnectionLayout.let {
if (networkStats) {
it.visibility = View.GONE
}
}
}
override fun onDestroyView() {
super.onDestroyView()
// adapter.isDestroyed = true
linearLayoutManager?.removeAllViews()
// adView.destroy()
linearLayoutManager = null
_binding = null
}
override fun onDetach() {
super.onDetach()
if(linearLayoutManager != null){
linearLayoutManager = null
}
}
override fun tellFragmentToGetItems() {
if (postViewModel.recyclerViewLayout.value.equals("titleLayout")
|| postViewModel.recyclerViewLayout.value.equals("gridLayout")
) {
hideShimmerEffect()
postViewModel.getPostListByLabel()
}
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.main, menu)
val searchManager =
requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
searchView.queryHint = resources.getString(R.string.searchForPosts)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(keyword: String): Boolean {
if (keyword.isEmpty()) {
Snackbar.make(
requireView(),
"please enter keyword to search",
Snackbar.LENGTH_SHORT
).show()
}
// itemArrayList.clear()
this#AccessoryFragment.keyword = keyword
requestSearchApi(keyword)
return false
}
override fun onQueryTextChange(newText: String): Boolean {
return false
}
})
searchView.setOnCloseListener {
if (keyword.isNullOrEmpty()) {
hideShimmerEffect()
return#setOnCloseListener false
}
if (Utils.hasInternetConnection(requireContext())) {
showShimmerEffect()
postViewModel.postListByLabelResponse = null
searchItemList.clear()
// adapter.differ.submitList(ArrayList())
linearLayoutManager?.removeAllViews()
binding.accessoryRecyclerView.removeAllViews()
// itemArrayList.clear()
adapter.differ.submitList(null)
postViewModel.finalURL.value =
BASE_URL_POSTS_BY_LABEL + "posts?labels=Accessory&maxResults=20&key=$API_KEY"
requestApiData()
// itemArrayList.clear()
// adapter.submitList(itemArrayList)
//====> Here I call the request api method again
Log.d(
TAG,
"setOnCloseListener: called ${adapter.differ.currentList.size.toString()}"
)
// adapter.notifyDataSetChanged()
// binding.progressBar.visibility = View.GONE
//
Log.d(TAG, "setOnCloseListener: ${postViewModel.finalURL.value.toString()}")
//
// adapter.notifyDataSetChanged()
// }
} else {
Log.d(TAG, "setOnCloseListener: called")
adapter.differ.submitList(null)
searchItemList.clear()
noInternetConnectionLayout()
}
false
}
postViewModel.searchError.observe(viewLifecycleOwner) { searchError ->
if (searchError) {
Toast.makeText(
requireContext(),
"There's no posts with this keyword", Toast.LENGTH_LONG
).show()
}
}
}
private fun requestSearchApi(keyword: String) {
if (Utils.hasInternetConnection(requireContext())) {
showShimmerEffect()
postViewModel.finalURL.value =
"${BASE_URL}?labels=Accessory&maxResults=500&key=$API_KEY"
postViewModel.getItemsBySearch()
postViewModel.searchedPostsResponse.observe(viewLifecycleOwner) { response ->
when (response) {
is NetworkResult.Success -> {
postsAPiFlag = false
// adapter.differ.currentList.clear()
if (searchItemList.isNotEmpty()) {
searchItemList.clear()
}
binding.progressBar.visibility = View.GONE
lifecycleScope.launch {
withContext(Dispatchers.Default) {
searchItemList.addAll(response.data?.items?.filter {
it.title.contains(keyword) || it.content.contains(keyword)
} as ArrayList<Item>)
}
}
Log.d(TAG, "requestSearchApi: test size ${searchItemList.size}")
if (searchItemList.isEmpty()) {
// adapter.differ.submitList(null)
Toast.makeText(
requireContext(),
"The search word was not found in any post",
Toast.LENGTH_SHORT
).show()
hideShimmerEffect()
return#observe
} else {
postsAPiFlag = false
// itemArrayList.clear()
adapter.differ.submitList(null)
hideShimmerEffect()
// Log.d(
//// TAG, "requestSearchApi: searchItemList ${searchItemList[0].title}"
// )
adapter.differ.submitList(searchItemList)
// binding.accessoryRecyclerView.scrollToPosition(0)
}
}
is NetworkResult.Error -> {
hideShimmerEffect()
binding.progressBar.visibility = View.GONE
Toast.makeText(
requireContext(),
response.message.toString(),
Toast.LENGTH_SHORT
).show()
Log.e(TAG, "onQueryTextSubmit: $response")
}
is NetworkResult.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
}
}
} else {
noInternetConnectionLayout()
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return if (menuItem.itemId == R.id.change_layout) {
changeAndSaveLayout()
true
} else false
}
}
Unless I'm missing something (that's a lot of code to go through!) you don't set any data on your adapter until this bit:
private fun requestApiData() {
postViewModel.getPostListByLabel()
postViewModel.postsResponse.observe(viewLifecycleOwner) {
...
adapter.differ.submitList(it.items.toList())
}
And getPostListByLabel() clears the current data in postsResponse
fun getPostListByLabel() = viewModelScope.launch {
getPostsByLabelSafeCall()
}
private suspend fun getPostsByLabelSafeCall() {
postsResponse.value = NetworkResult.Loading()
// fetch data over network and update postsResponse with it later
...
}
So when you first observe it, it's in the NetworkResult.Loading state - any posts you had stored have been wiped.
Your Fragment gets recreated when the Activity is rotated and destroyed - so if you're initialising the ViewModel data contents as part of that Fragment setup (like you're doing here) it's going to get reinitialised every time the Fragment is recreated, and you'll lose the current data.
You'll need to work out a way to avoid that happening - you don't actually want to do that clear-and-fetch whenever a Fragment is created, so you'll have to decide when it should happen. Maybe when the ViewModel is first created (i.e. through the init block), maybe the first time a Fragment calls it (e.g. create an initialised boolean in the VM set to false, check it in the call, set true when it runs). Or maybe just when postsResponse has no value yet (postsResponse.value == null). I don't know the flow of your application so you'll have to work out when to force a fetch and when to keep the data that's already there
I am developing a tracking application which tracks the path taken by user and plots a polyline on that
I am trying to add marker at certain position while tracking but the marker is not showing on the map
I have added the marker in onMapReady method but still it is not visible on the map
My source code
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
private var lastKnownLocation: Location? = null
private var locationPermissionGranted: Boolean = false
private lateinit var placesClient: PlacesClient
private lateinit var mMap: GoogleMap
private lateinit var binding: ActivityMapsBinding
private lateinit var fusedLocationProviderClient: FusedLocationProviderClient
private val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 121
private val REQUEST_CODE_ACTIVITY_RECOGNITION: Int = 0
val polylineOptions = PolylineOptions()
private val mapsActivityViewModel: MapsActivityViewModel by viewModels {
MapsActivityViewModelFactory(getTrackingRepository())
}
private fun getTrackingApplicationInstance() = application as TrackingApplication
private fun getTrackingRepository() = getTrackingApplicationInstance().trackingRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
Places.initialize(applicationContext, getString(R.string.maps_api_key))
placesClient = Places.createClient(this)
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
val isActivityRecognitionPermissionFree = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
val isActivityRecognitionPermissionGranted = EasyPermissions.hasPermissions(
this,
Manifest.permission.ACTIVITY_RECOGNITION
)
mapsActivityViewModel.lastTrackingEntity.observe(this) { lastTrackingEntity ->
lastTrackingEntity ?: return#observe
addLocationToRoute(lastTrackingEntity)
}
if (isActivityRecognitionPermissionFree || isActivityRecognitionPermissionGranted) {
setupLocationChangeListener()
} else {
EasyPermissions.requestPermissions(
host = this,
rationale = "For showing your step counts and calculate the average pace.",
requestCode = REQUEST_CODE_ACTIVITY_RECOGNITION,
perms = *arrayOf(Manifest.permission.ACTIVITY_RECOGNITION)
)
}
}
private fun addLocationToRoute(lastTrackingEntity: TrackingEntity) {
mMap.clear()
val newLatLng = lastTrackingEntity.asLatLng()
polylineOptions.points.add(newLatLng)
mMap.addPolyline(polylineOptions)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
getLocationPermission()
updateLocationUI()
getDeviceLocation()
mMap.addMarker(
MarkerOptions().position(LatLng(16.667421, 74.819688))
.title("Marker in Sydney")
.icon(
BitmapDescriptorFactory
.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)
)
)
}
val locationcallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
locationResult.locations.forEach {
val trackingEntity =
TrackingEntity(Calendar.getInstance().timeInMillis, it.latitude, it.longitude)
mapsActivityViewModel.insert(trackingEntity)
}
}
}
private fun setupLocationChangeListener() {
if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
val locationRequest = com.google.android.gms.location.LocationRequest()
locationRequest.priority =
com.google.android.gms.location.LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 5000 // 5000ms (5s)
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
return
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationcallback,
Looper.getMainLooper()
)
} else {
getLocationPermission()
}
}
#SuppressLint("MissingPermission")
private fun updateLocationUI() {
try {
if (locationPermissionGranted) {
mMap.isMyLocationEnabled = true
mMap.uiSettings.isMyLocationButtonEnabled = true
} else {
mMap.isMyLocationEnabled = false
mMap.uiSettings.isMyLocationButtonEnabled = false
lastKnownLocation = null
getLocationPermission()
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
#SuppressLint("MissingPermission")
private fun getDeviceLocation() {
try {
if (locationPermissionGranted) {
val locationResult = fusedLocationProviderClient.lastLocation
locationResult.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
lastKnownLocation = task.result
if (lastKnownLocation != null) {
mMap.moveCamera(
CameraUpdateFactory.newLatLngZoom(
LatLng(
lastKnownLocation!!.latitude,
lastKnownLocation!!.longitude
), 18.0F
)
)
}
} else {
val sydney = LatLng(-34.0, 151.0)
Log.d(TAG, "Current location is null. Using defaults.")
Log.e(TAG, "Exception: %s", task.exception)
mMap.moveCamera(
CameraUpdateFactory
.newLatLngZoom(sydney, 18.0F)
)
mMap.uiSettings.isMyLocationButtonEnabled = false
}
}
}
} catch (e: SecurityException) {
Log.e("Exception: %s", e.message, e)
}
}
private fun getLocationPermission() {
if (ContextCompat.checkSelfPermission(
this.applicationContext,
Manifest.permission.ACCESS_FINE_LOCATION
)
== PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
} else {
ActivityCompat.requestPermissions(
this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
locationPermissionGranted = false
when (requestCode) {
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION -> {
if (grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
locationPermissionGranted = true
}
}
}
updateLocationUI()
}
}
I've been experiencing a strange bug when retrieving a user location and displaying data based on the location retrieved in a recycler view. For context, whenever the application is started from fresh (no permissions granted) can retrieve the location. However, it doesn't display the expected content in the recycler view unless I close the application or press the bottom navigation bar.
Demonstration:
https://i.imgur.com/9kc1Zxc.gif
Fragment
const val TAG = "ForecastFragment"
#AndroidEntryPoint
class ForecastFragment : Fragment(), SearchView.OnQueryTextListener,
SwipeRefreshLayout.OnRefreshListener{
private val viewModel: WeatherForecastViewModel by viewModels()
private var _binding: FragmentForecastBinding? = null
private val binding get() = _binding!!
private lateinit var forecastAdapter: ForecastAdapter
private lateinit var searchMenuItem: MenuItem
private lateinit var searchView: SearchView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private var currentQuery: String? = null
private lateinit var client: FusedLocationProviderClient
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.forecast_list_menu, menu)
searchMenuItem = menu.findItem(R.id.menu_search)
searchView = searchMenuItem.actionView as SearchView
searchView.isSubmitButtonEnabled = true
searchView.setOnQueryTextListener(this)
return super.onCreateOptionsMenu(menu, inflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentForecastBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
client = LocationServices.getFusedLocationProviderClient(requireActivity())
swipeRefreshLayout = binding.refreshLayoutContainer
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
return binding.root
}
private fun updateSupportActionbarTitle() {
viewModel.queryMutable.observe(viewLifecycleOwner, Observer { query ->
(activity as AppCompatActivity).supportActionBar?.title = query
})
}
private fun setupRecyclerView() {
forecastAdapter = ForecastAdapter()
binding.forecastsRecyclerView.apply {
adapter = forecastAdapter
layoutManager = LinearLayoutManager(activity)
}
}
private fun requestWeatherApiData() {
lifecycleScope.launch {
viewModel.weatherForecast.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
// nothing for now
swipeRefreshLayout.isRefreshing = false
}
}
})
}
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun fetchForecastAsync(query: String?) {
if (query == null)
requestWeatherApiData()
else
searchWeatherApiData(query)
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (query != null) {
currentQuery = query
viewModel.queryMutable.value = query
searchWeatherApiData(query)
}
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
override fun onRefresh() {
currentQuery = viewModel.queryMutable.value
fetchForecastAsync(currentQuery)
}
private fun isLocationEnabled(): Boolean {
val locationManager: LocationManager =
requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}
private fun checkPermissions(): Boolean {
if (ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
return true
}
return false
}
private fun requestPermissions() {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ID)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_ID) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
getLastLocation()
}
}
}
#SuppressLint("MissingPermission")
private fun getLastLocation() {
if (checkPermissions()) {
if (isLocationEnabled()) {
client.lastLocation.addOnCompleteListener(requireActivity()) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
val geoCoder = Geocoder(requireContext(), Locale.getDefault())
val addresses = geoCoder.getFromLocation(
location.latitude, location.longitude, 1)
val city = addresses[0].locality
viewModel.queryMutable.value = city
getWeatherApiDataLocation(city)
}
}
} else {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
}
} else {
requestPermissions()
}
}
#SuppressLint("MissingPermission")
private fun requestNewLocationData() {
val mLocationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 0
fastestInterval = 0
numUpdates = 1
}
client = LocationServices.getFusedLocationProviderClient(requireActivity())
client.requestLocationUpdates(
mLocationRequest, mLocationCallback,
Looper.myLooper()
)
}
private val mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val mLastLocation: Location = locationResult.lastLocation
mLastLocation.latitude.toString()
mLastLocation.longitude.toString()
}
}
}
View Model
#HiltViewModel
class WeatherForecastViewModel
#Inject constructor(
private val repository: WeatherForecastRepository,
application: Application) :
AndroidViewModel(application) {
private val unit = "imperial"
private var query = "Paris"
val weatherForecast: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var searchWeatherForecastResponse: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
val weatherForecastLocation: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var queryMutable: MutableLiveData<String> = MutableLiveData()
init {
//queryMutable.value = query
//getWeatherForecast(queryMutable.value.toString(), unit)
}
private fun getWeatherForecast(query: String, units: String) =
viewModelScope.launch {
weatherForecast.postValue(Resource.Loading())
val response = repository.getWeatherForecast(query, units)
weatherForecast.postValue(weatherForecastResponseHandler(response))
}
private suspend fun searchWeatherForecastSafeCall(searchQuery: Map<String, String>) {
searchWeatherForecastResponse.postValue(Resource.Loading())
val response = repository.searchWeatherForecast(searchQuery)
searchWeatherForecastResponse.postValue(weatherForecastResponseHandler(response))
}
private suspend fun getWeatherForecastLocationSafeCall(query: Map<String, String>) {
weatherForecastLocation.postValue(Resource.Loading())
val response = repository.getWeatherForecastLocation(query)
weatherForecastLocation.postValue(weatherForecastResponseHandler(response))
}
fun searchWeatherForecast(searchQuery: Map<String, String>) =
viewModelScope.launch {
searchWeatherForecastSafeCall(searchQuery)
}
fun weatherForecastLocation(query: Map<String, String>) =
viewModelScope.launch {
getWeatherForecastLocationSafeCall(query)
}
fun applySearchQuery(searchQuery: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = searchQuery
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
fun applyLocationQuery(query: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = query
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
private fun weatherForecastResponseHandler(
response: Response<WeatherForecastResponse>): Resource<WeatherForecastResponse> {
if (response.isSuccessful) {
response.body()?.let { result ->
return Resource.Success(result)
}
}
return Resource.Error(response.message())
}
}
I feel the problem lies in the way, you are setting up your observers, you are setting them up in a function call, I believe that is wrong and that is setting up multiple observers for the same thing. It should just be setup once.
So I suggest you take out your observers to a separate function and call it once like observeProperties()
So your code will be like this
private fun observeProperties() {
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
Now you can call this method at the top as such
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
observeProperties()
Your other methods simply will be now the queries like this
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
}
I'm Joss this is my first question in stackoverflow
I want to activate(isEnabled = true) the button when a geofence event occurs.
The code that works sendNotification is woriking but I want to add function add button(btn_done) activate
GeofenceTransitionService.kt
class GeofenceTransitionService : IntentService("GeoTrIntentService") {
companion object {
private const val LOG_TAG = "GeoTrIntentService"
}
override fun onHandleIntent(intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.errorCode)
Log.e(LOG_TAG, errorMessage)
return
}
handleEvent(geofencingEvent)
}
private fun handleEvent(event: GeofencingEvent) {
if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
btn_done.isEnabled = true //How should I code this?
val reminder = getFirstReminder(event.triggeringGeofences)
val message = reminder?.message
val latLng = reminder?.latLng
if (message != null && latLng != null) {
sendNotification(this, message, latLng)
}
}
}
private fun getFirstReminder(triggeringGeofences: List<Geofence>): Reminder? {
val firstGeofence = triggeringGeofences[0]
return (application as
ReminderApp).getRepository().get(firstGeofence.requestId)
}
}
ReminderRepository.kt
class ReminderRepository(private val context: Context) {
companion object {
private const val PREFS_NAME = "ReminderRepository"
private const val REMINDERS = "REMINDERS"
}
private val preferences = context.getSharedPreferences(PREFS_NAME,
Context.MODE_PRIVATE)
private val gson = Gson()
private val geofencingClient = LocationServices.getGeofencingClient(context)
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceTransitionService::class.java)
PendingIntent.getService(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
}
fun add(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
// 1
val geofence = buildGeofence(reminder)
if (geofence != null
&& ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
// 2
geofencingClient
.addGeofences(buildGeofencingRequest(geofence),
geofencePendingIntent)
.addOnSuccessListener {
// 3
saveAll(getAll() + reminder)
success()
}
.addOnFailureListener {
// 4
failure(GeofenceErrorMessages.getErrorString(context,
it))
}
}
}
private fun buildGeofence(reminder: Reminder): Geofence? {
val latitude = reminder.latLng?.latitude
val longitude = reminder.latLng?.longitude
val radius = reminder.radius
if (latitude != null && longitude != null && radius != null) {
return Geofence.Builder()
.setRequestId(reminder.id)
.setCircularRegion(
latitude,
longitude,
radius.toFloat()
)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build()
}
return null
}
private fun buildGeofencingRequest(geofence: Geofence): GeofencingRequest {
return GeofencingRequest.Builder()
.setInitialTrigger(0)
.addGeofences(listOf(geofence))
.build()
}
fun remove(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
geofencingClient
.removeGeofences(listOf(reminder.id))
.addOnSuccessListener {
saveAll(getAll() - reminder)
success()
}
.addOnFailureListener {
failure(GeofenceErrorMessages.getErrorString(context, it))
}
}
private fun saveAll(list: List<Reminder>) {
preferences
.edit()
.putString(REMINDERS, gson.toJson(list))
.apply()
}
fun getAll(): List<Reminder> {
if (preferences.contains(REMINDERS)) {
val remindersString = preferences.getString(REMINDERS, null)
val arrayOfReminders = gson.fromJson(remindersString,
Array<Reminder>::class.java)
if (arrayOfReminders != null) {
return arrayOfReminders.toList()
}
}
return listOf()
}
fun get(requestId: String?) = getAll().firstOrNull { it.id == requestId }
fun getLast() = getAll().lastOrNull()
}
MainActivity.kt (show only important code)
class MainActivity : BaseActivity(), OnMapReadyCallback,
GoogleMap.OnMarkerClickListener {
companion object {
private const val MY_LOCATION_REQUEST_CODE = 329
private const val NEW_REMINDER_REQUEST_CODE = 330
private const val EXTRA_LAT_LNG = "EXTRA_LAT_LNG"
private const val LOG_TAG = "GeoTrIntentService"
fun newIntent(context: Context, latLng: LatLng): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_LAT_LNG, latLng)
return intent
}
}
private var map: GoogleMap? = null
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
newReminder.visibility = View.GONE
currentLocation.visibility = View.GONE
newReminder.setOnClickListener {
map?.run {
val intent = NewReminderActivity.newIntent(
this#MainActivity,
cameraPosition.target,
cameraPosition.zoom)
startActivityForResult(intent, NEW_REMINDER_REQUEST_CODE)
val geofencingEvent = GeofencingEvent.fromIntent(intent)
qwer(geofencingEvent)
}
}
btn_done.setOnClickListener {
val intent = Intent(this, sub::class.java)
startActivity(intent)
}
locationManager = getSystemService(Context.LOCATION_SERVICE) as
LocationManager
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_LOCATION_REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data:
Intent?) {
if (requestCode == NEW_REMINDER_REQUEST_CODE && resultCode ==
Activity.RESULT_OK) {
showReminders()
val reminder = getRepository().getLast()
map?.moveCamera(CameraUpdateFactory.newLatLngZoom(reminder?.latLng,
15f))
Snackbar.make(main, R.string.reminder_added_success,
Snackbar.LENGTH_LONG).show()
}
}
activate_maps.xml
<Button
android:id="#+id/btn_done"
android:enabled="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Arrive" />
You can use kotlin interface to change button state in app.
Create Interface for button state change in GeofenceTransitionService.kt
class GeofenceTransitionService : IntentService("GeoTrIntentService") {
interface ChangeViewState {
fun changeButtonState() : Button
}
companion object {
private const val LOG_TAG = "GeoTrIntentService"
}
override fun onHandleIntent(intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.errorCode)
Log.e(LOG_TAG, errorMessage)
return
}
handleEvent(geofencingEvent)
}
private val changeViewState : ChangeViewState? = null
private fun handleEvent(event: GeofencingEvent) {
if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
//btn_done.isEnabled = true //How should I code this?
changeViewState?.changeButtonState().isEnabled = true
val reminder = getFirstReminder(event.triggeringGeofences)
val message = reminder?.message
val latLng = reminder?.latLng
if (message != null && latLng != null) {
sendNotification(this, message, latLng)
}
}
}
private fun getFirstReminder(triggeringGeofences: List<Geofence>): Reminder? {
val firstGeofence = triggeringGeofences[0]
return (application as
ReminderApp).getRepository().get(firstGeofence.requestId)
}
}
Implement interface in main activity
class MainActivity : BaseActivity(), OnMapReadyCallback,
GoogleMap.OnMarkerClickListener, GeofenceTransitionService.ChangeViewState {
companion object {
private const val MY_LOCATION_REQUEST_CODE = 329
private const val NEW_REMINDER_REQUEST_CODE = 330
private const val EXTRA_LAT_LNG = "EXTRA_LAT_LNG"
private const val LOG_TAG = "GeoTrIntentService"
fun newIntent(context: Context, latLng: LatLng): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_LAT_LNG, latLng)
return intent
}
}
private var map: GoogleMap? = null
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// Button Ref
newReminder.visibility = View.GONE
currentLocation.visibility = View.GONE
newReminder.setOnClickListener {
map?.run {
val intent = NewReminderActivity.newIntent(
this#MainActivity,
cameraPosition.target,
cameraPosition.zoom
)
startActivityForResult(intent, NEW_REMINDER_REQUEST_CODE)
val geofencingEvent = GeofencingEvent.fromIntent(intent)
qwer(geofencingEvent)
}
}
btn_done.setOnClickListener {
val intent = Intent(this, sub::class.java)
startActivity(intent)
}
locationManager = getSystemService(Context.LOCATION_SERVICE) as
LocationManager
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_LOCATION_REQUEST_CODE
)
}
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data:
Intent?
) {
if (requestCode == NEW_REMINDER_REQUEST_CODE && resultCode ==
Activity.RESULT_OK
) {
showReminders()
val reminder = getRepository().getLast()
map?.moveCamera(
CameraUpdateFactory.newLatLngZoom(
reminder?.latLng,
15f
)
)
Snackbar.make(
main, R.string.reminder_added_success,
Snackbar.LENGTH_LONG
).show()
}
}
// Implement interface
override fun changeButtonState(): Button {
val button = findViewById(R.id.btn_done) as Button
return button
}
}