I am trying to request a user's location. The first time I request a user's location, it works as intended, with a permission rationale dialog that pops up and then the user is given a choice between fine and coarse location permission. However, if the user rejects the location permission, and then clicks on the find location button, I want the dialog to pop up again, however nothing happens. It seems like the OS is storing the user's choice, and not allowing for another attempt at finding the location. How do I fix this? Below is the relevant code.
package com.example.groupupandroid
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.iterator
import androidx.core.view.size
import androidx.fragment.app.Fragment
import com.example.groupupandroid.databinding.FragmentHomeScreenBinding
import com.example.groupupandroid.databinding.NavHeaderBinding
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.Marker
import com.google.android.gms.maps.model.MarkerOptions
import com.google.android.gms.tasks.OnSuccessListener
import com.google.android.material.navigation.NavigationView
import com.google.android.material.navigation.NavigationView.*
import java.util.jar.Manifest
class HomeScreenFragment : Fragment(), GoogleMap.OnMapLongClickListener,
GoogleMap.OnMarkerDragListener, GoogleMap.OnMyLocationButtonClickListener,
GoogleMap.OnMyLocationClickListener, OnNavigationItemSelectedListener{
private val callback = OnMapReadyCallback { googleMap ->
mMap = googleMap
mMap.mapType = GoogleMap.MAP_TYPE_NORMAL
mMap.uiSettings.isMyLocationButtonEnabled = false;
enableUserLocation()
}
private var requestLocationPermissions = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
permissions.forEach { actionMap ->
when (actionMap.key) {
android.Manifest.permission.ACCESS_COARSE_LOCATION -> {
if (actionMap.value) {
// permission granted continue the normal
// workflow of app
Log.i("DEBUG", "permission granted")
mMap.isMyLocationEnabled = true
zoomToUserLocation()
} else {
// if permission denied then check whether never
// ask again is selected or not by making use of
// !ActivityCompat.shouldShowRequest
// PermissionRationale(requireActivity(),
// Manifest.permission.CAMERA)
Log.i("DEBUG", "permission denied")
}
}
android.Manifest.permission.ACCESS_FINE_LOCATION -> {
if (actionMap.value) {
// permission granted continue the normal
// workflow of app
Log.i("DEBUG", "permission granted")
mMap.isMyLocationEnabled = true
zoomToUserLocation()
} else {
// if permission denied then check whether never
// ask again is selected or not by making use of
// !ActivityCompat.shouldShowRequest
// PermissionRationale(requireActivity(),
// Manifest.permission.CAMERA)
Log.i("DEBUG", "permission denied")
}
}
}
}
}
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var mMap: GoogleMap
private lateinit var mContext: Context
// private lateinit var mActivity: Activity
// Getting xml objects
private var binding: FragmentHomeScreenBinding? = null
private var headerBinding: NavHeaderBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeScreenBinding.inflate(layoutInflater)
headerBinding = NavHeaderBinding.inflate(layoutInflater)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(mContext)
// Inflate the layout for this fragment
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(callback)
binding?.menuButton?.setOnClickListener {
binding?.drawerLayout?.open()
}
binding?.navView?.setNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.HomeMenuItem -> {
// menuItem.isChecked = true
binding?.navView?.setCheckedItem(menuItem)
binding?.drawerLayout?.close()
}
R.id.ProfileMenuItem -> {
// menuItem.isChecked = true
binding?.navView?.setCheckedItem(menuItem)
binding?.drawerLayout?.close()
}
R.id.MyGroupsMenuItem -> {
// menuItem.isChecked = true
binding?.navView?.setCheckedItem(menuItem)
binding?.drawerLayout?.close()
}
R.id.SignOutMenuItem -> {
// menuItem.isChecked = true
binding?.navView?.setCheckedItem(menuItem)
binding?.drawerLayout?.close()
}
}
// Handle menu item selected
true
}
binding?.locationButton?.setOnClickListener {
enableUserLocation()
}
}
private fun enableUserLocation() {
// 1. Check if permissions are granted, if so, enable the my location layer
if (ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(mContext,
android.Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED) {
mMap.isMyLocationEnabled = true
zoomToUserLocation()
return
}
// 2. If a permission rationale dialog should be shown
if (ActivityCompat.shouldShowRequestPermissionRationale((activity as MainActivity),
android.Manifest.permission.ACCESS_FINE_LOCATION
) || ActivityCompat.shouldShowRequestPermissionRationale((activity as MainActivity),
android.Manifest.permission.ACCESS_COARSE_LOCATION
)
) {
val builder: AlertDialog.Builder = AlertDialog.Builder(mContext)
builder.setTitle(R.string.rationale_title)
.setMessage(R.string.rationale_desc)
.setPositiveButton("Ok") { _, _ ->
requestLocationPermissions.launch(locationPermissions)
}
builder.create().show()
return
}
// 3. Otherwise, request permission
requestLocationPermissions.launch(locationPermissions)
}
private fun zoomToUserLocation() {
fusedLocationClient.lastLocation
.addOnSuccessListener { location : Location? ->
// Got last known location. In some rare situations this can be null.
if (location!=null){
val userLocationLatLng = LatLng(location.latitude, location.longitude)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(userLocationLatLng, 13.0F))
}
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mContext = context
}
override fun onMapLongClick(p0: LatLng) {
TODO("Not yet implemented")
}
override fun onMarkerDrag(p0: Marker) {
TODO("Not yet implemented")
}
override fun onMarkerDragEnd(p0: Marker) {
TODO("Not yet implemented")
}
override fun onMarkerDragStart(p0: Marker) {
TODO("Not yet implemented")
}
override fun onMyLocationButtonClick(): Boolean {
TODO("Not yet implemented")
}
override fun onMyLocationClick(p0: Location) {
TODO("Not yet implemented")
}
private fun presentLocationNecessaryDialogue() {
val builder: AlertDialog.Builder = AlertDialog.Builder(mContext)
builder.setTitle(R.string.rationale_title)
.setMessage(R.string.rationale_desc)
.setPositiveButton("Ok") { _, _ ->
requestLocationPermissions.launch(locationPermissions)
}
builder.create().show()
}
companion object {
/**
* Request code for location permission request.
*
* #see .onRequestPermissionsResult
*/
private val locationPermissions = arrayOf(
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION
)}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
TODO("Not yet implemented")
}
}
Thank you in advance for your help.
Related
I apologize in advance for any of the amateur moves that I've made in the code below. This is my first Android app. It's a weather app and I am trying to implement a button that will retrieve the local weather based on where the phone is located. After tapping the button, the user will first be asked to allow location permission (coarse location), and then I'm attempting to use FusedLocationProviderClient to retrieve the latitude and longitude, which can then be used to make an api call to get the weather data. This is for a class, so I'm required to use something close to the approach I'm attempting to take in my code.
I'm 99% sure I have the location permission portion of the code working correctly. I've been running the app through the debugger and based on the path it's following, the permission is being requested and then is showing as permission granted when it should be showing as such. My problem is that something is wrong with the code that retrieves location data for the lat and lon values. I can see in the debugger that they are still null when I try to send them to the view model for the api call. (I've marked the line where I get a NullPointerException in the fragment below -- it's about 2/3 of the way down the file.)
One important thing that is worth noting: I have a red error line under "override" in this code in the fragment:
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
lat = location.latitude.toString()
lon = location.longitude.toString()
}
}
}
I have seen this basic code setup in several examples, so I'm assuming it doesn't always have the red line. The error says "onLocationResult overrides nothing." If I remove the question mark on that line, the red line goes away, but I don't know if doing that is part of what is causing my problem. I suspect that this code is the problem since I don't think the app ever gets to the lines that assign values to lat and lon. For the record, this is how I have to modify the code to get rid of the red line so the app can run (question mark removed and line after it removed, but I just commented it out here):
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
//locationResult ?: return
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
lat = location.latitude.toString()
lon = location.longitude.toString()
}
}
}
I've been looking at videos and reading answers on Stack Overflow for most of the day and haven't found anything that quite matches what I'm trying to do here. I've tried moving different methods from Activity to Fragment and back again. It always seems to come down to I'm simply not pulling in the location data properly to get those lat/lon values. At this point, I'm just hoping to find people who know more than I do to look at the code and see if something obvious is wrong. I feel like it might be something minor, but I could be wrong.
MainActivity:
package com.example.weatherapp
import android.Manifest
import android.app.AlertDialog
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat
import com.example.weatherapp.databinding.ActivityMainBinding
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback {
private val apiKey = "5025177c6bd1ce93f4ffa221fd7f7c8c"
private val REQUEST_LOCATION_PERMISSION = 1234
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
fun requestLocationPermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_COARSE_LOCATION)
) {
showLocationPermissionRationale()
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
}
private fun showLocationPermissionRationale() {
AlertDialog.Builder(this)
.setMessage(R.string.location_permission_rationale)
.setNeutralButton(R.string.ok) { _, _ ->
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
.create()
.show()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_LOCATION_PERMISSION) {
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission granted - Should I have something here??
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
}
SearchFragment:
package com.example.weatherapp
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Looper
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import com.example.weatherapp.databinding.FragmentSearchBinding
import com.google.android.gms.location.*
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
#AndroidEntryPoint
class SearchFragment : Fragment() {
private lateinit var binding: FragmentSearchBinding
#Inject lateinit var searchViewModel: SearchViewModel
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var locationRequest: LocationRequest
private lateinit var locationCallback: LocationCallback
private var lat: String? = null
private var lon: String? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
fusedLocationClient = LocationServices.getFusedLocationProviderClient(activity as MainActivity)
locationRequest = LocationRequest.create()
locationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
super.onLocationResult(locationResult)
for (location in locationResult.locations) {
lat = location.latitude.toString()
lon = location.longitude.toString()
}
}
}
binding = FragmentSearchBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().title = "Search"
searchViewModel.enableButton.observe(this) { enable ->
binding.button.isEnabled = enable
}
searchViewModel.showErrorDialog.observe(this) { showError ->
if (showError) {
ErrorDialogFragment().show(childFragmentManager, ErrorDialogFragment.TAG)
}
}
binding.zipCode.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
p0?.toString()?.let { searchViewModel.updateZipCode(it) }
}
override fun afterTextChanged(p0: Editable?) {
}
})
binding.button.setOnClickListener {
searchViewModel.submitButtonClicked()
if(!(searchViewModel.showErrorDialog.value!!)) {
val currentConditionsArg = SearchFragmentDirections.searchToCurrent(
searchViewModel.currentConditions.value,
searchViewModel.returnZipCode(),
null,
null
)
Navigation.findNavController(it).navigate(currentConditionsArg)
} else {
searchViewModel.resetErrorDialog()
}
}
// New location code starts here:
binding.localWeatherButton.setOnClickListener {
if (ContextCompat.checkSelfPermission(
(activity as MainActivity), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
startLocationUpdates()
searchViewModel.localWeatherButtonClicked(lat!!, lon!!) <- lat/lon still NULL!
if(!(searchViewModel.showErrorDialog.value!!)) {
val currentConditionsArg = SearchFragmentDirections.searchToCurrent(
searchViewModel.currentConditions.value,
null,
lat,
lon
)
Navigation.findNavController(it).navigate(currentConditionsArg)
} else {
searchViewModel.resetErrorDialog()
}
} else {
(activity as MainActivity).requestLocationPermission()
}
}
}
private fun startLocationUpdates() {
if (ContextCompat.checkSelfPermission(activity as MainActivity,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
fusedLocationClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.getMainLooper()
)
} else {
// permission not granted (though it should be since we wouldn't get here if it wasn't...)
}
}
}
In my app, when I click on an item (Issue) in a RecyclerView which uses FirebaseRecyclerPagingAdapter to paginate data from Firebase realtime database, it displays details about the item clicked in another fragment (using navigation component). This works fine on the first click, however when I return to the previous fragment and click the same item on the RecyclerView a second time, the details of the item are not shown.
Because I use safe args to pass the item id (issueId) to the next fragment which it uses to query the firebase realtime database and retrieve the details to be displayed, I decide to log the item id to my console in onViewCreated() just to be sure that the item id is being passed on the second click and also that the details (names of user who added an issue) are being retrieved from the database, but just not showing. Then, I noticed a weird behaviour.
On the first click, the item id is logged to the console, the details are logged to the console as well and the fragment displays the details. However on the second click, the item id is logged to the console (showing that the item id is being passed as should be the case), but the details are not logged to the console and not displayed in the fragment (hence the fragment shows up empty). Now the weird part, when I navigate back to the previous fragment, then I see a log of the details displayed twice.
Another strange thing I noticed is that, every item on the RecyclerView has this weird behaviour except the last item. The last item displays its details on the second click, but any other item I click doesn't.
I also noticed that the log shows the details for every item I have previously clicked twice when I navigate back even though I am clicking on a different item
I changed the adapter from FirebaseRecyclerPagingAdapter to FirebaseRecyclerAdapter, everything works fine. When I change back to using FirebaseRecyclerPagingAdapter, the same problem exists.
Is this a bug in my code or FirebaseRecyclerPagingAdapter itself. What could be the problem and what can I do to fix it?
Below is the FirebaseRecyclerPagingAdapter:
package com.colley.android.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.colley.android.R
import com.colley.android.databinding.ItemIssueBinding
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.firebase.ui.database.paging.FirebaseRecyclerPagingAdapter
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class IssuesPagingAdapter(
options: DatabasePagingOptions<Issue>,
private val context: Context,
private val currentUser: FirebaseUser?,
private val clickListener: IssuePagingItemClickedListener
) : FirebaseRecyclerPagingAdapter<Issue, IssuePagingViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IssuePagingViewHolder {
val viewBinding = ItemIssueBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return IssuePagingViewHolder(viewBinding)
}
override fun onBindViewHolder(viewHolder: IssuePagingViewHolder, position: Int, model: Issue) {
viewHolder.bind(currentUser, model, context, clickListener)
}
interface IssuePagingItemClickedListener {
fun onItemClick(issueId: String, view: View)
fun onItemLongCLicked(issueId: String, view: View)
fun onUserClicked(userId: String, view: View)
}
}
class IssuePagingViewHolder (private val itemBinding : ItemIssueBinding) : RecyclerView.ViewHolder(itemBinding.root) {
#SuppressLint("SetTextI18n")
fun bind(
currentUser: FirebaseUser?,
issue: Issue, context: Context,
clickListener: IssuesPagingAdapter.IssuePagingItemClickedListener) = with(itemBinding) {
//set issue title, body, timeStamp, contributions and endorsements count
issueTitleTextView.text = issue.title
issueBodyTextView.text = issue.body
issueTimeStampTextView.text = issue.timeStamp
contributionsTextView.text = issue.contributionsCount.toString()
endorsementTextView.text = issue.endorsementsCount.toString()
//check if userId is not null
issue.userId?.let { userId ->
//retrieve user profile
Firebase.database.reference.child("profiles").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//set the name of user who raised this issue
userNameTextView.text = profile.name
//set the school of the user who raised this issue
userSchoolTextView.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//retrieve user photo
Firebase.database.reference.child("photos").child(userId)
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
//set photo
if (photo != null) {
Glide.with(root.context).load(photo)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).into(userImageView)
} else {
Glide.with(root.context).load(R.drawable.ic_person).into(userImageView)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
root.setOnClickListener {
if(issue.issueId != null) {
clickListener.onItemClick(issue.issueId, it)
}
}
root.setOnLongClickListener {
if(issue.issueId != null) {
clickListener.onItemLongCLicked(issue.issueId, it)
}
true
}
userNameTextView.setOnClickListener {
if(issue.userId != null) {
clickListener.onUserClicked(issue.userId, it)
}
}
}
}
Here is the fragment to display the item details:
package com.colley.android.view.fragment
import android.os.Bundle
import android.util.Log
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.colley.android.R
import com.colley.android.adapter.IssuesCommentsRecyclerAdapter
import com.colley.android.databinding.FragmentViewIssueBinding
import com.colley.android.model.Comment
import com.colley.android.model.Issue
import com.colley.android.model.Profile
import com.colley.android.view.dialog.IssueCommentBottomSheetDialogFragment
import com.firebase.ui.database.FirebaseRecyclerOptions
import com.firebase.ui.database.ObservableSnapshotArray
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.*
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
class ViewIssueFragment :
Fragment(),
IssuesCommentsRecyclerAdapter.ItemClickedListener,
IssuesCommentsRecyclerAdapter.DataChangedListener {
private val args: ViewIssueFragmentArgs by navArgs()
private var _binding: FragmentViewIssueBinding? = null
private val binding get() = _binding
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private lateinit var recyclerView: RecyclerView
private lateinit var commentSheetDialog: IssueCommentBottomSheetDialogFragment
private var issue: Issue? = null
private var adapter: IssuesCommentsRecyclerAdapter? = null
private var manager: LinearLayoutManager? = null
private val uid: String
get() = currentUser.uid
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentViewIssueBinding.inflate(inflater, container, false)
recyclerView = binding?.issuesCommentsRecyclerView!!
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
//log item id
Log.d("Log itemId", args.issueId)
//get a query reference to issue comments //order by time stamp
val commentsRef = dbRef.child("issues").child(args.issueId)
.child("comments").orderByChild("commentTimeStamp")
//the FirebaseRecyclerAdapter class and options come from the FirebaseUI library
//build an options to configure adapter. setQuery takes firebase query to listen to and a
//model class to which snapShots should be parsed
val options = FirebaseRecyclerOptions.Builder<Comment>()
.setQuery(commentsRef, Comment::class.java)
.build()
//initialize issue comments adapter
adapter = IssuesCommentsRecyclerAdapter(
options,
currentUser,
this,
this,
requireContext())
manager = LinearLayoutManager(requireContext())
//reversing layout and stacking fron end so that the most recent comments appear at the top
manager?.reverseLayout = true
manager?.stackFromEnd = true
recyclerView.layoutManager = manager
recyclerView.adapter = adapter
dbRef.child("issues").child(args.issueId).addValueEventListener(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
issue = snapshot.getValue<Issue>()
if(issue != null) {
//listener for contrbutions count used to set count text
dbRef.child("issues").child(args.issueId)
.child("contributionsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.contributionsTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for endorsement counts used to set endorsement count text
dbRef.child("issues").child(args.issueId)
.child("endorsementsCount").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val count = snapshot.getValue<Int>()
if(count != null) {
binding?.endorsementTextView?.text = count.toString()
}
}
override fun onCancelled(error: DatabaseError) {} }
)
//set issue title, body and time stamp, these don't need to change
binding?.issueTitleTextView?.text = issue?.title
binding?.issueBodyTextView?.text = issue?.body
binding?.issueTimeStampTextView?.text = issue?.timeStamp.toString()
//listener for user photo
dbRef.child("photos").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val photo = snapshot.getValue<String>()
if(photo != null) {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(photo).into(
imageView
)
} }
} else {
context?.let { context -> binding?.userImageView?.let {
imageView ->
Glide.with(context).load(R.drawable.ic_profile).into(
imageView
)
} }
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
//listener for profile to set name and school
dbRef.child("profiles").child(issue?.userId.toString())
.addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val profile = snapshot.getValue<Profile>()
if (profile != null) {
//log name details to console
profile.name?.let { Log.d("Log Details", it) }
binding?.userNameTextView?.text = profile.name
binding?.userSchoolTextView?.text = profile.school
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
}
}
override fun onCancelled(error: DatabaseError) {}
}
)
binding?.commentLinearLayout?.setOnClickListener {
commentSheetDialog = IssueCommentBottomSheetDialogFragment(
requireContext(),
requireView())
commentSheetDialog.arguments = bundleOf("issueIdKey" to args.issueId)
commentSheetDialog.show(parentFragmentManager, null)
}
binding?.endorseLinearLayout?.setOnClickListener {
//update contributions count
dbRef.child("issues").child(args.issueId).child("endorsementsCount")
.runTransaction(
object : Transaction.Handler {
override fun doTransaction(currentData: MutableData): Transaction.Result {
//retrieve the current value of endorsement count at this location
var endorsementsCount = currentData.getValue<Int>()
if (endorsementsCount != null) {
//increase the count by 1
endorsementsCount++
//reassign the value to reflect the new update
currentData.value = endorsementsCount
}
//set database issue value to the new update
return Transaction.success(currentData)
}
override fun onComplete(
error: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
if (error == null && committed) {
Toast.makeText(requireContext(), "Endorsed", Toast.LENGTH_SHORT)
.show()
}
}
}
)
}
//view profile when clicked
binding?.userImageView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
//view user profile when clicked
binding?.userNameTextView?.setOnClickListener {
val action = issue?.userId?.let { it1 ->
ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(it1)
}
if (action != null) {
parentFragment?.findNavController()?.navigate(action)
}
}
}
override fun onItemClick(comment: Comment, view: View) {
//expand comment
}
override fun onItemLongCLicked(comment: Comment, view: View) {
//create option to delete
//create option to respond
}
//view user profile
override fun onUserClicked(userId: String, view: View) {
val action = ViewIssueFragmentDirections.actionViewIssueFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onStart() {
super.onStart()
adapter?.startListening()
}
override fun onStop() {
super.onStop()
adapter?.stopListening()
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onDataAvailable(snapshotArray: ObservableSnapshotArray<Comment>) {
//dismiss progress bar once snapshot is available
binding?.issuesCommentProgressBar?.visibility = GONE
//show that there are no comments if snapshot is empty else hide view
//show recycler view if snapshot is not empty else hide
if (snapshotArray.isEmpty()) {
binding?.noCommentsLayout?.visibility = VISIBLE
} else {
binding?.noCommentsLayout?.visibility = GONE
binding?.issuesCommentsRecyclerView?.visibility = VISIBLE
}
}
}
Here is the fragment with the recyclerView showing how I have initialised the adapter:
package com.colley.android.view.fragment
import android.os.Bundle
import android.view.*
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.paging.PagingConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.colley.android.R
import com.colley.android.adapter.IssuesPagingAdapter
import com.colley.android.databinding.FragmentIssuesBinding
import com.colley.android.model.Issue
import com.firebase.ui.database.paging.DatabasePagingOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class IssuesFragment :
Fragment(),
IssuesPagingAdapter.IssuePagingItemClickedListener {
private var _binding: FragmentIssuesBinding? = null
private val binding get() = _binding!!
private lateinit var dbRef: DatabaseReference
private lateinit var auth: FirebaseAuth
private lateinit var currentUser: FirebaseUser
private var adapter: IssuesPagingAdapter? = null
private var manager: LinearLayoutManager? = null
private lateinit var recyclerView: RecyclerView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private val uid: String
get() = currentUser.uid
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//fragment can participate in populating the options menu
setHasOptionsMenu(true)
//initialize Realtime Database
dbRef = Firebase.database.reference
//initialize authentication
auth = Firebase.auth
//initialize currentUser
currentUser = auth.currentUser!!
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
menu.clear()
inflater.inflate(R.menu.isssues_menu, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.search_issues_menu_item -> {
Toast.makeText(context, "Searching issues", Toast.LENGTH_LONG).show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentIssuesBinding.inflate(inflater, container, false)
recyclerView = binding.issueRecyclerView
swipeRefreshLayout = binding.swipeRefreshLayout
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//get a query reference to issues
val issuesQuery = dbRef.child("issues")
//configuration for how the FirebaseRecyclerPagingAdapter should load pages
val config = PagingConfig(
pageSize = 30,
prefetchDistance = 15,
enablePlaceholders = false
)
//Options to configure an FirebasePagingAdapter
val options = DatabasePagingOptions.Builder<Issue>()
.setLifecycleOwner(viewLifecycleOwner)
.setQuery(issuesQuery, config, Issue::class.java)
.setDiffCallback(object : DiffUtil.ItemCallback<DataSnapshot>() {
override fun areItemsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java)?.issueId == newItem.getValue(Issue::class.java)?.issueId
}
override fun areContentsTheSame(
oldItem: DataSnapshot,
newItem: DataSnapshot
): Boolean {
return oldItem.getValue(Issue::class.java) == newItem.getValue(Issue::class.java)
}
})
.build()
//instantiate adapter
adapter = IssuesPagingAdapter(
options,
requireContext(),
currentUser,
this)
//Perform some action every time data changes or when there is an error.
viewLifecycleOwner.lifecycleScope.launch {
adapter?.loadStateFlow?.collectLatest { loadStates ->
when (loadStates.refresh) {
is LoadState.Error -> {
// The initial load failed. Call the retry() method
// in order to retry the load operation.
Toast.makeText(context, "Error fetching issues! Retrying..", Toast.LENGTH_SHORT).show()
//display no posts available at the moment
binding.noIssuesLayout.visibility = VISIBLE
adapter?.retry()
}
is LoadState.Loading -> {
// The initial Load has begun
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
// The previous load (either initial or additional) completed
swipeRefreshLayout.isRefreshing = false
//remove display no posts available at the moment
binding.noIssuesLayout.visibility = GONE
}
}
when (loadStates.append) {
is LoadState.Error -> {
// The additional load failed. Call the retry() method
// in order to retry the load operation.
adapter?.retry()
}
is LoadState.Loading -> {
// The adapter has started to load an additional page
// ...
swipeRefreshLayout.isRefreshing = true
}
is LoadState.NotLoading -> {
if (loadStates.append.endOfPaginationReached) {
// The adapter has finished loading all of the data set
swipeRefreshLayout.isRefreshing = false
}
}
}
}
}
//set recycler view layout manager
manager = LinearLayoutManager(requireContext())
recyclerView.layoutManager = manager
//initialize adapter
recyclerView.adapter = adapter
swipeRefreshLayout.setOnRefreshListener {
adapter?.refresh()
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
//navigate to new fragment with issue id
override fun onItemClick(issueId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToViewIssueFragment(issueId)
parentFragment?.findNavController()?.navigate(action)
}
override fun onItemLongCLicked(issueId: String, view: View) {
}
override fun onUserClicked(userId: String, view: View) {
val action = HomeFragmentDirections.actionHomeFragmentToUserInfoFragment(userId)
parentFragment?.findNavController()?.navigate(action)
}
}
Before click
After first click
After second click
Use addListenerForSingleValueEvent instead of addValueEventListener to query the item details (issue) from the database in the fragment that displays the clicked item details. Otherwise, remove the addValueEventListener in onStop() so that the listener is no longer attached to the database when navigating back to the previous fragment.
I am very new to Kotlin and Android Development and really wanting to get into it. I am doing a project to help me understand and work with intent. The goal is simple, an android app that gets the users location in one activity, then passes it on to the next activity, where I intend to use it ( in the form of longitude and latitude). However I have been trying for hours to get the data to pass as an extra. The error that is show says nullpointer:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Intent.getStringExtra(java.lang.String)' on a null object reference
at com.tba.mypoint_ofinterest.LocationInfo.<init>(LocationInfo.kt:11)
The two files relevant to this are:
AddLocation.kt - This is where I get the users location. Then the user presses a button: "btn_accept" and then I want it go to the LocationInfo.kt activity.
LocationInfo.kt-This is where I want to receive the location data and where the error message keeps getting thrown.
Here is the AddLocation.kt code:
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.*
import kotlinx.android.synthetic.main.activity_add_location.*
class AddLocation : AppCompatActivity() {
//variables needed for location grab
lateinit var fusedLocationProviderClient: FusedLocationProviderClient
lateinit var locationRequest: LocationRequest
lateinit var locationCallback: LocationCallback
var REQUEST_CODE = 1000
lateinit var userLocation: Location
//Deal with permissions
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE -> {
if (grantResults.size > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
Toast.makeText(this, "Permission Granted", Toast.LENGTH_SHORT).show()
else
Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show()
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_location)
//Check for permission!
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
)
)
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
else {
buildLocationRequest()
buildLocationCallback()
//Create fused provider client
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
//get location
//start getting location
btnGetLocation.setOnClickListener(View.OnClickListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
return#OnClickListener
}
fusedLocationProviderClient.requestLocationUpdates(
locationRequest, locationCallback,
Looper.myLooper()
)
//make the button invisible after clicked
btnGetLocation.visibility = View.INVISIBLE
})
}
//Listen for clicking add location then turn off GPS and proceed to next view
btn_accept.setOnClickListener {
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
}
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
userLocation = p0!!.locations.get(p0!!.locations.size - 1) //get last location
}
}
var long = userLocation.longitude
var lat = userLocation.latitude
addInfo(long,lat)
}
}
private fun buildLocationCallback() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
var location = p0!!.locations.get(p0!!.locations.size - 1) //get last location
userLocation = location
txtLocation.text =
location.latitude.toString() + "/" + location.longitude.toString() + "and accuracy" + location.accuracy.toString()
}
}
}
private fun buildLocationRequest() {
locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 5000
locationRequest.fastestInterval = 3000
locationRequest.smallestDisplacement = 10f
}
//This gives intent and takes the GPS data to the next view to be combined with user input
fun addInfo(long:Double,lat:Double) {
val infoIntent: Intent = Intent(this, LocationInfo::class.java).apply {
putExtra("LAT_DATA", lat)
putExtra("LONG_DATA",long)
}
startActivity(infoIntent)
}
//Stop getting location data if back button is pressed
override fun onSupportNavigateUp(): Boolean {
fusedLocationProviderClient.removeLocationUpdates(locationCallback)
return super.onSupportNavigateUp()
}
}
Here is the LocationInfo.kt code:
import android.content.Intent
import android.location.Location
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_location_info.*
class LocationInfo : AppCompatActivity() {
val longitude = intent.getStringExtra("LONG_DATA")
val latitude = intent.getStringExtra("LAT_DATA")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_location_info)
btnSaveLocation.setOnClickListener{view ->saveInfo(view)}
textView2.text = longitude.toString()+"/"+latitude.toString()
}
fun saveInfo(x: View?){
val saveIntent: Intent = Intent(this,MainActivity::class.java)
startActivity(saveIntent)
}
}
I don't think I really understand how to use intent properly or I am mislabeling the loction data somewhere. Any advice is welcome, thank you
The issue is you are trying to access the intent even before the onCreate() is called, as a result intent is null.
Follow this,
class LocationInfo : AppCompatActivity() {
private lateinit var longitude:Double
private lateinit var latitude:Double
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_location_info)
btnSaveLocation.setOnClickListener{view ->saveInfo(view)}
longitude = intent.getDoubleExtra("LONG_DATA",0.0)
latitude = intent.getDoubleExtra("LAT_DATA",0.0)
textView2.text = longitude.toString()+"/"+latitude.toString()
}
fun saveInfo(x: View?){
val saveIntent: Intent = Intent(this,MainActivity::class.java)
startActivity(saveIntent)
}
}
You should access the Intent in the onCreate() method.
val longitude = intent.getDoubleExtra("LONG_DATA",0.0)
val latitude = intent.getDoubleExtra("LAT_DATA",0.0)
I am having a strange issue where I have an android app that uses google map to display markers of places on google map. The locations come from an API via my websites then the application plot the points on the map. This works perfectly when run from android to studio to virtual and physical devices but the app is deployed to play store. I am not sure why. The image below show it on physical device and virtual device.
Image with a virtual device with markers showing on map
Code for google maps activity
package kryptic.kode.diseaseprediction.ui
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.InterstitialAd
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import kryptic.kode.diseaseprediction.R
import kryptic.kode.diseaseprediction.databinding.ActivityMapsBinding
import kryptic.kode.diseaseprediction.maps.LocationHelper
class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
//binding
private lateinit var binding: ActivityMapsBinding
lateinit var mGoogleMap: GoogleMap
lateinit var locationHelper: LocationHelper
var mapFrag: SupportMapFragment? = null
var mFusedLocationClient: FusedLocationProviderClient? = null
private lateinit var mInterstitialAd: InterstitialAd
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//setContentView(R.layout.activity_maps)
binding = ActivityMapsBinding.inflate(layoutInflater)
setContentView(binding.root)
MobileAds.initialize(this)
/*load Ads */
mInterstitialAd = InterstitialAd(this)
mInterstitialAd.adUnitId = getString(R.string.interstitial_ads)
mInterstitialAd.adListener = object : AdListener() {
override fun onAdClosed() {
mInterstitialAd.loadAd(AdRequest.Builder().build())
}
}
mInterstitialAd.loadAd(AdRequest.Builder().build())
if (mInterstitialAd.isLoaded) {
mInterstitialAd.show()
} else {
Log.d("TAG", "The interstitial wasn't loaded yet.")
}
/*end Load ads*/
supportActionBar?.title = "Doctors Near By"
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
mapFrag = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFrag?.getMapAsync(this)
}
public override fun onPause() {
super.onPause()
//stop location updates when Activity is no longer active
mFusedLocationClient?.removeLocationUpdates(locationHelper.mLocationCallback)
}
override fun onMapReady(googleMap: GoogleMap) {
//ads
mInterstitialAd.loadAd(AdRequest.Builder().build())
if (mInterstitialAd.isLoaded) {
mInterstitialAd.show()
} else {
Log.d("TAG", "The interstitial wasn't loaded yet.")
}
mGoogleMap = googleMap
mGoogleMap.mapType = GoogleMap.MAP_TYPE_NORMAL
this.locationHelper = LocationHelper(this, mGoogleMap,getString(R.string.google_place_key))
//Check the build version to enable certain features
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
//Location Permission already granted
mFusedLocationClient?.requestLocationUpdates(
locationHelper.mLocationRequest,
locationHelper.mLocationCallback,
Looper.myLooper()
)
mGoogleMap.isMyLocationEnabled = true
} else {
//Request Location Permission
//checkLocationPermission()
locationHelper.checkLocationPermission()
}
} else {
mFusedLocationClient?.requestLocationUpdates(
locationHelper.mLocationRequest,
locationHelper.mLocationCallback,
Looper.myLooper()
)
mGoogleMap.isMyLocationEnabled = true
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
MY_PERMISSIONS_REQUEST_LOCATION -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// location-related task you need to do.
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
mFusedLocationClient?.requestLocationUpdates(
locationHelper.mLocationRequest,
locationHelper.mLocationCallback,
Looper.myLooper()
)
mGoogleMap.setMyLocationEnabled(true)
}
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
Toast.makeText(this, "permission denied", Toast.LENGTH_LONG).show()
}
return
}
}// other 'case' lines to check for other
// permissions this app might request
}
companion object {
val MY_PERMISSIONS_REQUEST_LOCATION = 99
}
}
Any ideas why this is happening ?
I want to get current location when I touch a button.
Touch a button
requestLocationUpdates one time, and get current location, then removeLocationUpdates.
The problem is in 2. I don't want GPS and wifi work all time when I requestLocationUpdates. But I don't know how to removeLocationUpdates in
locationCallback.
package com.example.location
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.util.Log
import android.widget.Button
import android.widget.TextView
import com.google.android.gms.location.*
class MainActivity : AppCompatActivity() {
private var userAgreePermissionCode = 1
private lateinit var userLocationClient: FusedLocationProviderClient
private lateinit var userLocationCallback: LocationCallback
#SuppressLint("RestrictedApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val somePermission = arrayOf(android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION)
for (p in somePermission ){
if(ActivityCompat.checkSelfPermission(this, p) != PackageManager.PERMISSION_GRANTED)
ActivityCompat.requestPermissions(this, somePermission , userAgreePermissionCode)
break
}
findViewById<Button>(R.id.Locate).setOnClickListener{
userLocationClient = LocationServices.getFusedLocationProviderClient(this)
val userLocationRequest = LocationRequest().apply {
interval = 1000
fastestInterval = 1000
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
// Problem is here
userLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
locationResult ?: return
for (location in locationResult.locations){
// I want to get the first update location(current location) and close requestLocationUpdates!!!!
// Only one time, not keep update
Log.i("longitude", location.longitude.toString())
Log.i("latitude", location.latitude.toString())
// findViewById<TextView>(R.id.Longitude).text = location.longitude.toString()
// findViewById<TextView>(R.id.Latitude).text = location.latitude.toString()
// Problem here, it's error.
userLocationClient.removeLocationUpdates(userLocationCallback)
}
}
}
userLocationClient.requestLocationUpdates(userLocationRequest,userLocationCallback,null)
}
}
// -----------------------------------------
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
when (requestCode) {
userAgreePermissionCode -> {
for( i in 0..(grantResults.size-1) ){
if ((grantResults.isNotEmpty() && grantResults[i] == PackageManager.PERMISSION_GRANTED))
Log.i("Status:", "Agree a permission")
else
finish()
}
return
}
}
}
// -----------------------------------------
}
After requesting you will get callback
You are calling userLocationClient.removeLocationUpdates(userLocationCallback)
in loop. need to call after break outside loop after getting location object
https://developer.android.com/training/location/receive-location-updates.html#stop-updates
To use removeLocationUpdates from within an anonymous LocationListener object, you can do removeLocationUpdates(this).