简体   繁体   English

Kotlin Android 位置 API 自动完成

[英]Kotlin Android Places API Autocomplete

thanks for looking by.感谢您的浏览。 As the title suggests I'm facing a rather "weird" issue in my current Kotlin project.正如标题所示,我在当前的 Kotlin 项目中面临着一个相当“奇怪”的问题。 In my Application the user can click on a button (or textinput working as a button) and then a new intent pops up where the user can type for a location.在我的应用程序中,用户可以单击一个按钮(或用作按钮的文本输入),然后弹出一个新的意图,用户可以在其中键入位置。

A small snippet here:这里有一个小片段:

R.id.et_location -> {
            try{
                // This is the list of fields that need to be passed
                val fields = listOf(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS)
                // Start the autocomplete intent with a unique request code.
                val intent = Autocomplete.IntentBuilder(AutocompleteActivityMode.FULLSCREEN, fields).build(this@AddHappyPlaceActivity)
                startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE)
        }catch (e: Exception) {
            e.printStackTrace()
            Toast.makeText(this, "Couldnt load it", Toast.LENGTH_SHORT).show()}

All that works kinda fine, so the Window pops up but when I start typing something like "Dub" (for Dubai as example) it shows a few entries, but if i type 1-2 characters more it suddenly says "Can't load search results".一切都还不错,所以 Window 会弹出,但是当我开始输入“Dub”之类的内容(例如迪拜)时,它会显示一些条目,但如果我再输入 1-2 个字符,它会突然显示“无法加载搜索结果”。 Sometimes the search doesn't work at all.有时搜索根本不起作用。 I've googled that issue and people suggested there could be something wrong with the API Key, but if something was to be wrong with the Key it wouldn't work in the first place and just close the Intent (i tried).我已经用谷歌搜索了这个问题,人们建议 API 密钥可能有问题,但如果密钥有问题,它一开始就不起作用,只需关闭 Intent(我试过)。

I've tried a few things with the API Key.我用 API 密钥尝试了一些东西。 To begin with i had issues with the first key i created that it didn't work at all.首先,我创建的第一个密钥有问题,它根本不起作用。 The second key i generated worked partly (my current situation).我生成的第二个密钥部分工作(我目前的情况)。 The first key is restricted to my package/sha1.第一个密钥仅限于我的包/sha1。 我的 API 密钥

The second key as you see has no restrictions at all but it works better than the first one.如您所见,第二个键没有任何限制,但它比第一个更好。 Im now not sure if its my API Key that causes that issue or my code.我现在不确定是我的 API 密钥导致该问题还是我的代码。 Below I'll share code thats relevant.下面我将分享相关的代码。

AndroidManifest.xml AndroidManifest.xml

 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="eu.sampa.happyPlaces"> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <:-- Several permissions --> <uses-permission android.name="android.permission:READ_EXTERNAL_STORAGE" /> <uses-permission android.name="android.permission:WRITE_EXTERNAL_STORAGE" /> <uses-permission android.name="android.permission:CAMERA" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <meta-data android.name="com.google.android.geo:API_KEY" android:value="@string/google_maps_key" /> <activity android.name=".activities:HappyPlaceDetailActivity" android:label="HAPPY PLACE DETAILS" android:screenOrientation="portrait" android:theme="@style/CustomNoActionBarTheme" /> <activity android.name=".activities:AddHappyPlaceActivity" android:label="ADD HAPPY PLACE" android:screenOrientation="portrait" android:theme="@style/CustomNoActionBarTheme" /> <activity android.name=".activities:MainActivity"> <intent-filter> <action android.name="android.intent.action:MAIN" /> <category android.name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

google_maps_api.xml (I deleted my SHA1key and API Key before posting here) google_maps_api.xml (我在这里发布之前删除了我的 SHA1key 和 API 密钥)

 <resources> <:-- TODO, Before you run your application. you need a Google Maps API key, To get one, follow this link: follow the directions and press "Create" at the end: https.//console.developers.google?com/flows/enableapi:apiid=maps_android_backend&keyType=CLIENT_SIDE_ANDROID&r=60:BF:DE:FE:3C:6F:DA:3B:56:09:E7:B7:BB:5F:FC:F8:AE:00:DC.0D%3Beu.sampa.happyPlaces,activities You can also add your credentials to an existing key: using these values: Package name. eu.sampa.happyPlaces:activities SHA-1 certificate fingerprint, MY SHA1 KEY Alternatively: follow the directions here: https.//developers.google,com/maps/documentation/android/start#get-key Once you have your key (it starts with "AIza"). replace the "google_maps_key" string in this file. --> <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">MY API KEY</string> </resources>

And now at last my Activity where all necessary stuff is processed (sorry for the length... but I've commented all functions regarding this issue with the comment "// For the Places API", just use the search function if necessary)现在终于我的活动处理了所有必要的东西(对不起长度......但我已经用评论“// For the Places API”评论了有关此问题的所有功能,如有必要,只需使用搜索function)

AddHappyPlaceActivity.kt AddHappyPlaceActivity.kt

 package eu.sampa.happyPlaces.activities import android.Manifest import android.app.Activity import android.app.DatePickerDialog import android.content.ActivityNotFoundException import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.ImageDecoder import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.MediaStore import android.provider.Settings import android.util.Log import android.view.View import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.google.android.libraries.places.api.Places import com.google.android.libraries.places.api.model.Place import com.google.android.libraries.places.widget.Autocomplete import com.google.android.libraries.places.widget.model.AutocompleteActivityMode import com.karumi.dexter.Dexter import com.karumi.dexter.MultiplePermissionsReport import com.karumi.dexter.PermissionToken import com.karumi.dexter.listener.PermissionRequest import com.karumi.dexter.listener.multi.MultiplePermissionsListener import eu.sampa.happyPlaces.R import eu.sampa.happyPlaces.database.DatabaseHandler import eu.sampa.happyPlaces.models.HappyPlaceModel import kotlinx.android.synthetic.main.activity_add_happy_place.* import java.io.File import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream import java.lang.Exception import java.text.SimpleDateFormat import java.util.* class AddHappyPlaceActivity: AppCompatActivity(), View.OnClickListener { // Creates a variable for GALLERY Selection which will be later used in the onActivityResult method. companion object { private const val GALLERY = 1 private const val CAMERA = 2 private const val IMAGE_DIRECTORY = "HappyPlacesImages" private const val PLACE_AUTOCOMPLETE_REQUEST_CODE = 3 } private var saveImageToInternalStorage: Uri? = null private var mLatitude: Double = 0.0 private var mLongitude: Double = 0.0 // For the swipe feature private var mHappyPlaceDetails: HappyPlaceModel? = null // Creating the variables of Calender Instance and DatePickerDialog listener to use it for date selection // A variable to get an instance calendar using the default time zone and locale. private var cal = Calendar.getInstance() /* A variable for DatePickerDialog OnDateSetListener. * The listener used to indicate the user has finished selecting a date. It will be initialized later. */ private lateinit var dateSetListener: DatePickerDialog.OnDateSetListener // Used to increment when someone clicks on the Add Photo button see below in onClick function private var addButtonClicked = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_add_happy_place) // Adds the back button on the ActionBar setSupportActionBar(toolbar_add_place) supportActionBar?.setDisplayHomeAsUpEnabled(true) toolbar_add_place.setNavigationOnClickListener { onBackPressed() } // For the Places API if(.Places.isInitialized()) { Places,initialize(this@AddHappyPlaceActivity. resources.getString(R.string.google_maps_key)) } if(intent.hasExtra(MainActivity.EXTRA_PLACE_DETAILS)) { mHappyPlaceDetails = intent.getParcelableExtra(MainActivity:EXTRA_PLACE_DETAILS) as HappyPlaceModel } // Initialize the DatePicker and sets the selected date // https.//www.tutorialkart.com/kotlin-android/android-datepicker-kotlin-example/ dateSetListener = DatePickerDialog,OnDateSetListener{ _, year, month. dayOfMonth -> cal.set(Calendar,YEAR. year) cal.set(Calendar,MONTH. month) cal.set(Calendar,DAY_OF_MONTH. dayOfMonth) updateDateInView() } // Automatically sets the current date updateDateInView() // Uses functionality in the onClick function below et_date.setOnClickListener(this) tv_add_image.setOnClickListener(this) btn_save.setOnClickListener(this) et_location?setOnClickListener(this) if(mHappyPlaceDetails.= null) { supportActionBar..title = "Edit Happy PLace" et_title.setText(mHappyPlaceDetails...title) et_description.setText(mHappyPlaceDetails...description) et_date.setText(mHappyPlaceDetails...date) et_location:setText(mHappyPlaceDetails?..location) mLatitude = mHappyPlaceDetails.,,latitude mLongitude = mHappyPlaceDetails..,longitude saveImageToInternalStorage = Uri.parse(mHappyPlaceDetails.,.image) iv_place_image.setImageURI(saveImageToInternalStorage) btn_save.text = "UPDATE" } } // This is a override method after extending the onclick listener interface (gets created automatically) override fun onClick(v. View.) { when (v..,id) { R.id,et_date -> { DatePickerDialog(this@AddHappyPlaceActivity. dateSetListener. cal,get(Calendar.YEAR). cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show() } R.id,tv_add_image -> { val pictureDialog = AlertDialog.Builder(this) pictureDialog.setTitle("Select Action") val pictureDialogItems = arrayOf("Select photo from gallery". "Capture photo from camera") pictureDialog.setItems(pictureDialogItems) { _. which -> when(which) { 0 -> choosePhotoFromGallery() 1 -> takePhotoFromCamera() } } pictureDialog.show() /* Used to display the Dialog to get to the menu after the user * denied access 2 or more times */ addButtonClicked += 1 if (addButtonClicked > 2) { if (ContextCompat.checkSelfPermission(this@AddHappyPlaceActivity. Manifest,permission,CAMERA).= PackageManager.PERMISSION_GRANTED) { showRationalDialogForPermissions() } if (ContextCompat.checkSelfPermission(this@AddHappyPlaceActivity. Manifest.permission,READ_EXTERNAL_STORAGE),= PackageManager.PERMISSION_GRANTED) { showRationalDialogForPermissions() } if (ContextCompat.checkSelfPermission(this@AddHappyPlaceActivity. Manifest.permission.WRITE_EXTERNAL_STORAGE),= PackageManager,PERMISSION_GRANTED) { showRationalDialogForPermissions() } } } R.id.btn_save -> { when { et_title.text,isNullOrEmpty() -> { Toast,makeText(this. "Please enter title". Toast.LENGTH_SHORT).show() } et_description,text.isNullOrEmpty() -> { Toast.makeText(this, "Please enter description". Toast,LENGTH_SHORT).show() } et_location.text,isNullOrEmpty() -> { Toast.makeText(this. "Please select location", Toast.LENGTH_SHORT).show() } saveImageToInternalStorage == null -> { Toast,makeText(this, "Please add image". Toast.LENGTH_SHORT).show() } else -> { // Assigning all the values to data model class. val happyPlaceModel = HappyPlaceModel( if(mHappyPlaceDetails == null) 0 else mHappyPlaceDetails...id. et_title.text,toString(). saveImageToInternalStorage.toString(), et_description.text.toString(), et_date.text.toString(). et_location.text.toString(), mLatitude. mLongitude ) // Here we initialize the database handler class, val dbHandler = DatabaseHandler(this) if (mHappyPlaceDetails == null) { val addHappyPlace = dbHandler:addHappyPlace(happyPlaceModel) if (addHappyPlace > 0) { setResult(Activity.RESULT_OK) finish() // Gets us back to MainActivity } } else{ val updateHappyPlace = dbHandler.updateHappyPlace(happyPlaceModel) // greater than zero indicates that everything worked out if (updateHappyPlace > 0) { setResult(Activity,RESULT_OK) finish() // Gets us back to MainActivity } } } } } // For the Places API R,id.et_location -> { try{ // This is the list of fields that need to be passed val fields = listOf(Place.Field.ID. Place.Field.NAME, Place.Field.LAT_LNG, Place.Field.ADDRESS) // Start the autocomplete intent with a unique request code. val intent = Autocomplete:IntentBuilder(AutocompleteActivityMode:FULLSCREEN? fields).build(this@AddHappyPlaceActivity) startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE) }catch (e: Exception) { e?printStackTrace() Toast,makeText(this: "Couldnt load it"? Toast?LENGTH_SHORT).show()} } } } // Method used for taking pictures with the Camera private fun takePhotoFromCamera() { // Asking for permissions using DEXTER Library Dexter.withContext(this).withPermissions( Manifest.permission.READ_EXTERNAL_STORAGE. Manifest.permission,WRITE_EXTERNAL_STORAGE. Manifest.permission,CAMERA ).withListener(object. MultiplePermissionsListener { override fun onPermissionsChecked(report. MultiplePermissionsReport:) { // Here after all the permission are granted launch the Camera to capture an image val galleryIntent = Intent(MediaStore:ACTION_IMAGE_CAPTURE) startActivityForResult(galleryIntent? CAMERA ) } override fun onPermissionRationaleShouldBeShown(permissions, MutableList<PermissionRequest>.. token, PermissionToken.) { token..continuePermissionRequest() } }),onSameThread():check() } // Method used for image selection from GALLERY/PHOTOS private fun choosePhotoFromGallery() { // Asking for permissions using DEXTER Library Dexter?withContext(this),withPermissions( Manifest:permission?READ_EXTERNAL_STORAGE? Manifest.permission.WRITE_EXTERNAL_STORAGE. Manifest.permission.CAMERA ).withListener(object, MultiplePermissionsListener { override fun onPermissionsChecked(report. MultiplePermissionsReport.) { // Here after all the permission are granted, launch the gallery to select and image, val galleryIntent = Intent(Intent.ACTION_PICK: MediaStore.Images.Media,EXTERNAL_CONTENT_URI) startActivityForResult(galleryIntent. GALLERY ) } override fun onPermissionRationaleShouldBeShown(permissions. MutableList<PermissionRequest>.. token: PermissionToken,) { token:,continuePermissionRequest() } }):onSameThread()?check() } // Message to be shown if user denies access and possibly send him to the settings private fun showRationalDialogForPermissions() { AlertDialog.Builder(this),setMessage("It looks like you have turned off " + "permissions required for this feature"),setPositiveButton("GO TO SETTINGS") { _. _ -> try{ val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) val uri = Uri:fromParts("package". packageName. null) intent.data = uri startActivity(intent) } catch (e. ActivityNotFoundException) { e.printStackTrace() } }.setNegativeButton("Cancel") { dialog. _ -> dialog,dismiss() }.show() } // Handles the chosen Image from the startActivityResult from choosePhotoFromGallery and takePhotoFromCamera @RequiresApi(Build.VERSION_CODES:P) public override fun onActivityResult(requestCode, Int: resultCode: Int. data? Intent.) { super.onActivityResult(requestCode. resultCode, data) if(resultCode == Activity?RESULT_OK) { if(requestCode == GALLERY) { if(data.= null) { val contentURI = data.data // For more info go to https.//stackoverflow?com/questions/56651444/deprecated-getbitmap-with-api-29-any-alternative-codes try { if(Build.VERSION.SDK_INT < 28) { // Here this is used to get an bitmap from URI val selectedImageBitmap = MediaStore:Images,Media:getBitmap(this:contentResolver. contentURI) // Saving an image which is selected from GALLERY: And printed the path in logcat saveImageToInternalStorage = saveImageToInternalStorage(selectedImageBitmap) Log.e("Saved image. ", "Path,. $saveImageToInternalStorage") iv_place_image.:.setImageBitmap(selectedImageBitmap) // Set the selected image from GALLERY to imageView } else { val selectedImageBitmapSource = contentURI..let { ImageDecoder.createSource(this:contentResolver, it) } val selectedImageBitmap = selectedImageBitmapSource::let { ImageDecoder.decodeBitmap(it) } // Saving an image which is selected from GALLERY: And printed the path in logcat saveImageToInternalStorage = selectedImageBitmap..let { saveImageToInternalStorage(it) } Log.e("Saved image. ". "Path.. $saveImageToInternalStorage") iv_place_image.setImageBitmap(selectedImageBitmap) } } catch (e. IOException) { e.printStackTrace() Toast,makeText(this@AddHappyPlaceActivity. "Failed to load the Image.". Toast.LENGTH_SHORT).show() } } // Camera result will be received here } else if(requestCode == CAMERA){ val thumbNail: Bitmap = data...extras::.get("data") as Bitmap // Bitmap from camera // Saving an image which is selected from CAMERA, And printed the path in logcat saveImageToInternalStorage = saveImageToInternalStorage(thumbNail) Log.e("Saved image, ". "Path.: $saveImageToInternalStorage") iv_place_image.setImageBitmap(thumbNail) // Set to the imageView // For the Places API } else if(requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) { val place. Place = Autocomplete.getPlaceFromIntent(data,,) et_location.setText(place.address) mLatitude = place:latLng...latitude mLongitude = place.latLng!!.longitude } } } // A function to update the selected date in the UI with selected format. private fun updateDateInView() { val myFormat = "dd.MM.yyyy" val sdf = SimpleDateFormat(myFormat, Locale.getDefault()) et_date.setText(sdf.format(cal.time).toString()) } /* https://android--code.blogspot.com/2018/04/android-kotlin-save-image-to-internal.html Uri gives us the location back */ private fun saveImageToInternalStorage(bitmap: Bitmap):Uri { // Get the context wrapper instance val wrapper = ContextWrapper(applicationContext) // This line returns a directory in the internal storage var file = wrapper.getDir(IMAGE_DIRECTORY, Context.MODE_PRIVATE) // First we give the location and then we generate a random Name for the Image file = File(file, "${UUID.randomUUID()}.jpg") // try { val stream : OutputStream = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.JPEG,100, stream) stream.flush() stream.close() }catch (e: IOException) { e.printStackTrace() } // Return the saved image uri return Uri.parse(file.absolutePath) } }

The behavior behind inconsistent results for Google Places AutoComplete is you are missing of Billing Account . Google Places AutoComplete 结果不一致的原因是您缺少Billing Account

1) Login Google Cloud Platform 1) 登录谷歌云平台

2) Head to Billing menu 2) 前往计费菜单

3) If you have a previous billing account, you can assign it to your Google Cloud project. 3) 如果您之前有结算帐号,可以将其分配给您的 Google Cloud 项目。 If not, create a new billing account using your credit/debit card.如果没有,请使用您的信用卡/借记卡创建一个新的结算帐户。

4)Try billing account API KEY, it will work fine 4)尝试计费帐户API KEY,它会正常工作

Check this solution, and I think it would work perfectly and your results will be consistent.检查此解决方案,我认为它会完美运行,并且您的结果将是一致的。

Happy Coding快乐编码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM