Android BLE ScanList unFixed Layout - android

I'm going to make an Android Bluetooth scan list, and in the first picture, there are two layouts that surround Device.name, and I want to copy only the Device.name part into several, but the whole part is copied and works like the second picture.
pic1
pic2
Attach XML and BluetoothDevicescan code.
<activity_device_scanlist.xml>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_weight="3"
android:background="#4472C4"
android:weightSum="14.5">
<ImageView
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginTop="15dp"
android:layout_marginBottom="15dp"
android:layout_weight="6.1"
android:src="#drawable/ino05" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="10sp">
<TextView
android:id="#+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="device.name"
android:textSize="24dp" />
<TextView
android:id="#+id/device_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="device.address"
android:textSize="12dp" />
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true" />
</RelativeLayout>
</LinearLayout>
<BluetoothDeviceScan.kt>
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ListActivity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import com.android.gassmarter.R
import java.util.*
/**
* Activity for scanning and displaying available Bluetooth LE devices.
*/
// 출처 : https://github.com/googlearchive/android-BluetoothLeGatt/blob/master/Application/src/main/java/com/example/android/bluetoothlegatt/DeviceScanActivity.java
class BluetoothDeviceScan : ListActivity() {
private var mLeDeviceListAdapter: LeDeviceListAdapter? = null
private var mBluetoothAdapter: BluetoothAdapter? = null
private var mScanning: Boolean = false
private var mHandler: Handler? = null
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
val k_f = intent.getBooleanExtra("kill", false)
if (k_f == true) {
Log.d("", "true")
finish()
} else
Log.d("", "false")
}
#RequiresApi(Build.VERSION_CODES.M)
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mHandler = Handler()
// Use this check to determine whether BLE is supported on the device. Then you can
// selectively disable BLE-related features.
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show()
finish()
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
mBluetoothAdapter = bluetoothManager.adapter
// Checks if Bluetooth is supported on the device.
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show()
finish()
return
}
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
#SuppressLint("MissingPermission")
override fun onResume() {
super.onResume()
// Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled,
// fire an intent to display a dialog asking the user to grant permission to enable it.
if (!mBluetoothAdapter!!.isEnabled) {
if (!mBluetoothAdapter!!.isEnabled) {
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT)
}
}
// Initializes list view adapter.
mLeDeviceListAdapter = LeDeviceListAdapter()
listAdapter = mLeDeviceListAdapter
scanLeDevice(true)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish()
return
}
super.onActivityResult(requestCode, resultCode, data)
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
override fun onPause() {
super.onPause()
scanLeDevice(false)
mLeDeviceListAdapter!!.clear()
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
#SuppressLint("MissingPermission")
override fun onListItemClick(l: ListView, v: View, position: Int, id: Long) {
val device = mLeDeviceListAdapter!!.getDevice(position) ?: return
val intent = Intent(this, BluetoothConnect::class.java)
intent.putExtra(BluetoothConnect.EXTRAS_DEVICE_NAME, device.name)
intent.putExtra(BluetoothConnect.EXTRAS_DEVICE_ADDRESS, device.address)
if (mScanning) {
mBluetoothAdapter!!.stopLeScan(mLeScanCallback)
mScanning = false
}
startActivity(intent)
}
#RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
#SuppressLint("MissingPermission")
private fun scanLeDevice(enable: Boolean) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler!!.postDelayed({
mScanning = false
mBluetoothAdapter!!.stopLeScan(mLeScanCallback)
// invalidateOptionsMenu() // onCreateOptionsMenu호출
}, SCAN_PERIOD)
mScanning = true
// 05-01 : 블루투스 디바이스 검색할 때 UUID기반으로 검색이 가능하게 변경함
var uuid = arrayOf( UUID.fromString("0000ffb0-0000-1000-8000-00805f9b34fb") )
mBluetoothAdapter!!.startLeScan(uuid, mLeScanCallback)
} else {
mScanning = false
mBluetoothAdapter!!.stopLeScan(mLeScanCallback)
}
// invalidateOptionsMenu() // onCreateOptionsMenu호출
}
// Adapter for holding devices found through scanning.
private inner class LeDeviceListAdapter : BaseAdapter() {
private val mLeDevices: ArrayList<BluetoothDevice>
private val mInflator: LayoutInflater
init {
mLeDevices = ArrayList<BluetoothDevice>()
mInflator = this#BluetoothDeviceScan.layoutInflater
}
fun addDevice(device: BluetoothDevice) {
if (!mLeDevices.contains(device)) {
mLeDevices.add(device)
}
}
fun getDevice(position: Int): BluetoothDevice? {
return mLeDevices[position]
}
fun clear() {
mLeDevices.clear()
}
// 자식 뷰들의 개수를 리턴함
override fun getCount(): Int {
return mLeDevices.size
}
// 어댑터 뷰의 자식 뷰가 n개라면, 어댑터 객체가 갖는 항목의 개수 역시 n개이다.
// getItem 메소드는 항목들 중 하나를 리턴한다.
override fun getItem(i: Int): Any {
return mLeDevices[i] // i값이 2라면 2번째 항목을 리턴함
}
// 어댑터가 갖는 항목의 ID를 리턴하는 메소드
override fun getItemId(i: Int): Long {
return i.toLong() // i값이 2라면 2번째 항목을 리턴함
}
#SuppressLint("MissingPermission")
// 자식 뷰중 하나를 리턴하는 메소드
// convertView 파라미터의 값을 확인해서 생성되었는지 확인할 수 있다.
// 값이 null이면 자식 뷰를 생성한다.
override fun getView(i: Int, convertView: View?, viewGroup: ViewGroup): View {
// Log.d("View 공부중", "getView 호출")
var view = convertView
val viewHolder: ViewHolder
// convertView가 null이면 Holder 객체를 생성하고
// 생성한 Holder 객체에 inflating 한 뷰의 참조값을 저장
if (view == null) {
view = mInflator.inflate(R.layout.activity_device_scanlist, null)
viewHolder = ViewHolder()
viewHolder.deviceAddress = view!!.findViewById(R.id.device_address) as TextView
viewHolder.deviceName = view.findViewById(R.id.device_name) as TextView
// View의 태그에 Holder 객체를 저장
view.tag = viewHolder
} else {
// convertView가 null이 아니면 뷰를 생성할때 태그에 저장했던 Holder 객체가 존재
// 이 Holder 객체는 자신을 inflating한 참조값(다시 전개할 필요가 없다.)
viewHolder = view.tag as ViewHolder
return view
}
// 속성값 변경
val device = mLeDevices[i]
val deviceName = device.name
if (deviceName != null && deviceName.length > 0)
viewHolder.deviceName!!.text = deviceName
viewHolder.deviceAddress!!.text = device.address
return view
}
}
// Device scan callback.
//디바이스 scan 콜백 디바이스를 스캔.
#SuppressLint("MissingPermission")
private val mLeScanCallback = BluetoothAdapter.LeScanCallback { device, rssi, scanRecord ->
runOnUiThread {
// Log.d("View 공부중", "mLeScanCallback 호출")
// 04/27 - 이름 없는 장치는 성능 안 잡아먹게 아예 빼버림
if(device.name != null) {
// Log.d("View 공부중", device.name)
mLeDeviceListAdapter!!.addDevice(device)
mLeDeviceListAdapter!!.notifyDataSetChanged()
}
}
}
// 전개된 뷰의 참조값을 저장할 객체
internal class ViewHolder {
var deviceName: TextView? = null
var deviceAddress: TextView? = null
}
companion object {
private val REQUEST_ENABLE_BT = 1
// Stops scanning after 10 seconds.
private val SCAN_PERIOD: Long = 5000 // 블루투스 스캔하는 시간 -> 단위 : ms
}
}
I searched to solve the problem, but I couldn't find the algorithm.
how to solve this problem ?

Related

Android with Kotlin recyclerView with checkboxes randomly checked after delete list item

apologies for my limited knowledge of programming and any sloppiness. I have a reyclerview with alarm objects that I can add and it creates them. When I add say 4 alarms, and delete three of them. The last alarms checkbox is checked by itself. I can not in anyway use the checkbox.setChecked() method for some reason. android studio is not recognizing it, if anyone could please let me know why that is. Also if you know of a solution to the auto check on the last alarm object please.
package com.example.alarmclock
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.Checkable
import android.widget.EditText
import android.widget.TextView
import androidx.core.widget.doAfterTextChanged
import androidx.core.widget.doBeforeTextChanged
import androidx.recyclerview.widget.RecyclerView
import java.security.Key
class AlarmAdapter (private val alarmList: MutableList<Alarm>) : RecyclerView.Adapter<AlarmAdapter.ViewHolder>() {
//start viewholder
inner class ViewHolder(alarm: View) : RecyclerView.ViewHolder(alarm) {
val alarmLabel = itemView.findViewById<EditText>(R.id.alarmLabel)
val editTextTime = itemView.findViewById<EditText>(R.id.editTextTime)
val textView1 = itemView.findViewById<TextView>(R.id.textView1)
val deleteCheckBox = itemView.findViewById<Button>(R.id.deleteAlarmCheckBox)
//val deleteButton = itemView.findViewById<Button>(R.id.deleteAlarmButton)
//val addButton = itemView.findViewById<Button>(R.id.addAlarmButton)
val mondayCheckBox = itemView.findViewById<Button>(R.id.mondayCheckBox)
val tuesdayCheckBox = itemView.findViewById<Button>(R.id.tuesdayCheckBox)
val wednesdayCheckBox = itemView.findViewById<Button>(R.id.wednesdayCheckBox)
val thursdayCheckBox = itemView.findViewById<Button>(R.id.thursdayCheckBox)
val fridayCheckBox = itemView.findViewById<Button>(R.id.fridayCheckBox)
val saturdayCheckBox = itemView.findViewById<Button>(R.id.saturdayCheckBox)
val sundayCheckBox = itemView.findViewById<Button>(R.id.sundayCheckBox)
val amCheckBox = itemView.findViewById<Button>(R.id.amCheckBox)
val pmCheckBox = itemView.findViewById<Button>(R.id.pmCheckBox)
}//end viewholder
fun addAlarm (alarm: Alarm) {
alarmList.add(alarm)
notifyItemInserted(alarmList.size - 1)
}
fun returnAlarmList (): MutableList<Alarm> {
return alarmList
}
fun removeAlarms() {
alarmList.removeAll {
alarm -> alarm.deleteCheck == true
}
//notifyDataSetChanged()
}
fun deleteAlarm (deletedAlarmList: List<Int> ) {
val deletedListIterator = deletedAlarmList.iterator()
val alarmListIterator = alarmList.iterator()
while (deletedListIterator.hasNext()){
while (alarmListIterator.hasNext()){
if (deletedListIterator.next() == alarmListIterator.next().alarmId){
alarmList.remove(alarmListIterator.next())
}
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val context = parent.context
val inflater = LayoutInflater.from(context)
val alarmView = inflater.inflate(R.layout.alarms, parent, false)
return ViewHolder(alarmView)
}
override fun getItemCount(): Int {
return alarmList.size
}
override fun onBindViewHolder(holder: AlarmAdapter.ViewHolder, position: Int) {
val alarm: Alarm = alarmList[position]
val alarmLabel = holder.alarmLabel
var textView1 = holder.textView1
var editTextTime = holder.editTextTime
var mondayCheckBox = holder.mondayCheckBox
var tuesdayCheckBox = holder.tuesdayCheckBox
var wednesdayCheckBox = holder.wednesdayCheckBox
var thursdayCheckBox = holder.thursdayCheckBox
var fridayCheckBox = holder.fridayCheckBox
var saturdayCheckBox = holder.saturdayCheckBox
var sundayCheckBox = holder.sundayCheckBox
var amCheckBox = holder.amCheckBox
var pmCheckBox = holder.pmCheckBox
var deleteAlarmCheckBox = holder.deleteCheckBox
var lastCharacter = ""
var secondLastCharacter = ""
deleteAlarmCheckBox.setOnClickListener {
alarm.deleteCheck = !alarm.deleteCheck
}
alarmLabel.doAfterTextChanged {
alarm.alarmLabel = alarmLabel.text.toString()
textView1.text = alarm.alarmLabel
}
editTextTime.doAfterTextChanged {
//lastCharacter = editTextTime.text.get(editTextTime.text.length-1).toString()
textView1.text = lastCharacter
if (editTextTime.text.length == 2 && secondLastCharacter != ":"){
//if (lastCharacter != ":") {
editTextTime.setText(editTextTime.text.toString().plus(":"))
editTextTime.setSelection(editTextTime.text.length)
//}
}
editTextTime.doBeforeTextChanged { _, _, _, _ ->
if (editTextTime.length() != 0) {
secondLastCharacter = editTextTime.text.get(editTextTime.text.length - 1).toString()
}
}
if (editTextTime.text.length == 5 ){
alarm.hour = editTextTime.text.get(0).toString().plus(editTextTime.text.get(1).toString())
if (alarm.hour.toInt() < 10) alarm.hour = "0".plus(alarm.hour)
///////
var inputedTimeList = editTextTime.text.toList()
val timeIterator = inputedTimeList.iterator()
}
}
mondayCheckBox.setOnClickListener {
alarm.monday = !alarm.monday
textView1.text = alarm.monday.toString()
}
tuesdayCheckBox.setOnClickListener {
alarm.tuesday = !alarm.tuesday
}
wednesdayCheckBox.setOnClickListener {
alarm.wednesday = !alarm.wednesday
}
thursdayCheckBox.setOnClickListener {
alarm.thursday = !alarm.thursday
}
fridayCheckBox.setOnClickListener {
alarm.friday = !alarm.friday
}
saturdayCheckBox.setOnClickListener {
alarm.saturday = !alarm.saturday
}
sundayCheckBox.setOnClickListener {
alarm.sunday = !alarm.sunday
}
amCheckBox.setOnClickListener {
alarm.amPm = !alarm.amPm
}
}
}
The answer is quite simple, RecyclerView items are reused, so be sure that you set the all the values onBindViewHolder, because after your item is deleted, the actual view is not, so previously set values might be preset although they are not correct according to your data.
The easiest way would be to have isChecked Boolean value store in the Alarm object, onBindViewHolder always set the isChecked param on the Checkbox according to the value returned from the Alarm and when you change the isChecked inside the Checkbox listener - make sure you also update the value inside the Alarm object.
Another solution would be calling notifyDatasetChanged() on the RecyclerView, but it's definitely not the best solution especially if you have dynamic row deletion (and possibly a neat animation).
P.S. consider using viewBinding in your project, it will save you time writing all that ugly findViewById code :)))

Android Kotlin: startActivityForResult & Insert to RecylerView

I am attempting to use startActivityForResult to return information that can be used as data to be entered into a RecyclerView in the original Activity.
In Activity 1, I have set an onClickListener for a button. Once the button is pressed Activity 2 is launched with some options. The User fills in these options and then they are returned to the Activity 1.
Activity 1 then inserts an entry into the RecyclerView with information recieved from the button press and from the options returned from Activity 2.
The problem is that once Activity 2 finishes and returns back to Activity 1 the entry is not added to the RecyclerView as it should. How can I fix my code so that the entry is added correctly?
OrderActivity.kt - Activity 1
package com.example.pos
import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.multiplerecyclerview.OrderAdapter
import kotlinx.android.synthetic.main.activity_order.*
const val FILE_ID = 1
class OrderActivity : AppCompatActivity() {
val list = ArrayList<DataModel>()
// Adapter class is initialized and list is passed in the param.
val orderAdapter = OrderAdapter(this, getItemsList())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order)
margButton.setOnClickListener {
val food = "Margerita"
val price = 11.90
Toast.makeText(this, "$food clicked", Toast.LENGTH_LONG).show()
val intent = Intent(this#OrderActivity, PizzaActivity::class.java) /* Creating an Intent to go to Dashboard. */
intent.putExtra("foodName", "$food")
intent.putExtra("foodPrice", "$price")
startActivityForResult(intent, FILE_ID) /* Starting Activity. */
Log.i("Order", "startActivityResult.\n")
}
//Set the LayoutManager that this RecyclerView will use.
orderRecyclerView.layoutManager = LinearLayoutManager(this)
//adapter instance is set to the recyclerview to inflate the items.
orderRecyclerView.adapter = orderAdapter
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.i("Order", "OnActivityResult.\n")
if(requestCode == FILE_ID && resultCode == Activity.RESULT_OK) {
val foodPrice = intent.getStringExtra("foodPrice")
val foodSize = intent.getStringExtra("foodSize")
insertItem(view = margButton, foodType = "Margherita", price = foodPrice!!.toDouble(), size = foodSize!!)
Log.i("Order", "startActivityResult success\n")
} else {
Log.i("Order", "onActivityResult failed\n")
}
}
fun insertItem(view: View, foodType: String, price: Double, size: String) {
val index = 0
Toast.makeText(this, "Margerita clicked", Toast.LENGTH_LONG).show()
val newItem = DataModel("$foodType", "$size", "$price", viewType = OrderAdapter.NO_TOPPING)
list.add(index, newItem)
orderAdapter.notifyItemInserted(index)
}
private fun getItemsList(): ArrayList<DataModel> {
list.add(DataModel("Romana","1","12.50", "Pepperoni", "Aubergine", "Ex Mozz.", "Salami", OrderAdapter.TOPPINGS_4))
list.add(DataModel("American","1","12.50", viewType = OrderAdapter.NO_TOPPING))
return list
}
}
PizzaActivity.kt - Activity 2
package com.example.pos
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import com.example.multiplerecyclerview.OrderAdapter
import kotlinx.android.synthetic.main.activity_order.*
import kotlinx.android.synthetic.main.activity_pizza.*
class PizzaActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_pizza)
val ss1:String = intent.getStringExtra("foodName").toString()
val ss2:String = intent.getStringExtra("foodPrice").toString()
val textView1: TextView = findViewById<TextView>(R.id.pizzaName)
textView1.text = ss1
val textView2: TextView = findViewById<TextView>(R.id.pizzaPrice)
val textView3: TextView = findViewById<TextView>(R.id.pizzaSize)
val editText1: EditText = findViewById<EditText>(R.id.topping1Entry)
val editText2: EditText = findViewById<EditText>(R.id.topping2Entry)
val editText3: EditText = findViewById<EditText>(R.id.topping3Entry)
val editText4: EditText = findViewById<EditText>(R.id.topping4Entry)
tenInchButton.setOnClickListener {
Toast.makeText(this, "Ten inch selected", Toast.LENGTH_LONG).show()
insertSize(size = "10", textView = textView3)
setPrice(price = 9.9, textView = textView2)
}
twelveInchButton.setOnClickListener {
Toast.makeText(this, "Twelve inch selected", Toast.LENGTH_LONG).show()
insertSize(size = "12", textView = textView3)
setPrice(price = 11.8, textView = textView2)
}
additionalButton.setOnClickListener {
var cost = .90
if(textView3.equals("10")) {
changePrice(price = cost, textView = textView2)
} else {
cost = 1.1
changePrice(price = cost, textView = textView2)
}
}
premiumButton.setOnClickListener {
var cost = 1.3
if(textView3.equals("10")) {
changePrice(price = cost, textView = textView2)
} else {
cost = 1.5
changePrice(price = cost, textView = textView2)
}
}
completeBtn.setOnClickListener {
emptyAdditions(editText1)
val intent = Intent(this#PizzaActivity, OrderActivity::class.java) /* Creating an Intent to go to Dashboard. */
intent.putExtra("foodPrice", "$textView2")
intent.putExtra("foodSize", "$textView3")
if (!emptyAdditions(editText1) || !emptyAdditions(editText2) || !emptyAdditions(editText3) || !emptyAdditions(editText4)) {
intent.putExtra("topping1", "$editText1")
intent.putExtra("topping2", "$editText2")
intent.putExtra("topping3", "$editText3")
intent.putExtra("topping4", "$editText4")
setResult(Activity.RESULT_OK, intent)
} else {
setResult(Activity.RESULT_OK, intent)
}
finish()
}
}
private fun emptyAdditions(editText: EditText): Boolean {
val msg: String = editText.text.toString()
//check if the EditText have values or not
return msg.trim().isNotEmpty()
}
private fun insertSize(size: String, textView: TextView) {
textView.text = size
}
private fun setPrice(price: Double, textView: TextView) {
textView.text = price.toString()
}
private fun changePrice(price: Double, textView: TextView) {
var cost = textView.text.toString()
var total = (cost.toDouble() + price)
textView.text = total.toString()
}
}
Here is Activity 1. The Buttons are on the left and the RecyclerView is on the right. Once the Margherita Pizza is pressed you are brought to Activity 2.
Here is Activity 2. Once you have selected a Size you can press complete Pizza. Once you press complete Pizza it returns you to Activity 1 and the 12 inch Margerita is added to the RecyclerView.
Error Message:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.pos, PID: 30885
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { cmp=com.example.pos/.OrderActivity (has extras) }} to activity {com.example.pos/com.example.pos.OrderActivity}: kotlin.KotlinNullPointerException
at android.app.ActivityThread.deliverResults(ActivityThread.java:4268)
at android.app.ActivityThread.handleSendResult(ActivityThread.java:4312)
at android.app.ActivityThread.-wrap19(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1644)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: kotlin.KotlinNullPointerException
at com.example.pos.OrderActivity.onActivityResult(OrderActivity.kt:58)
at android.app.Activity.dispatchActivityResult(Activity.java:7276)
at android.app.ActivityThread.deliverResults(ActivityThread.java:4264)

Android Animated Carousel TextView within RecyclerView width wider than screen text gets clipped off. Even when animated into view

I am building a stock ticker type carousel that has market prices running along the screen as shown. The 'CRC' Ticker has only the partial view showing, it clips off at the edge of the parent which is the width of the device even when it is animated into view. I want it to have a width large enough hold all the children that goes past the device width.
Here if the Carousel layout xml:
<?xml version="1.0" encoding="utf-8"?>
<com.android.forexwatch.views.timepanel.CarouselView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/carouselLinear"
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal"
android:singleLine="true"
android:animateLayoutChanges="true"
/>
Here is the class:
package com.android.forexwatch.views.timepanel
import android.content.Context
import android.util.AttributeSet
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.widget.RelativeLayout
import androidx.appcompat.widget.LinearLayoutCompat
import com.android.forexwatch.model.Index
import com.android.forexwatch.R
import com.android.forexwatch.model.CityInfoDetailed
import com.github.ybq.android.spinkit.SpinKitView
class CarouselView(context: Context, attrs: AttributeSet): LinearLayoutCompat(context, attrs) {
companion object {
private const val IndexWidth = 600F
}
private var contextThemeWrapper: ContextThemeWrapper? = null
init {
contextThemeWrapper = ContextThemeWrapper(context.applicationContext, R.style.Theme_ForexWatch)
}
fun setupView ( cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
createPriceTickerViews(cityInfo, isMirror)
}
private fun createPriceTickerViews(cityInfo: CityInfoDetailed?, isMirror: Boolean = false) {
destroy()
removeAllViews()
var calculatedWidth = 0
cityInfo?.indexes?.forEachIndexed {
index, element ->
if (isMirror) index.plus(cityInfo?.indexes.count())
val loader: SpinKitView = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.preloader, null) as SpinKitView
val priceTicker: PriceTicker = LayoutInflater.from(contextThemeWrapper).inflate(R.layout.price_ticker, null) as PriceTicker
addView(priceTicker)
addView(loader)
// priceTicker.x = IndexWidth * index
calculatedWidth.plus(IndexWidth)
priceTicker.initialize(element, cityInfo, loader)
}
layoutParams.width = calculatedWidth
}
private fun destroy() {
for (idx in 0 .. this.childCount) {
val view = getChildAt(idx)
if (view is PriceTicker) {
view.destroy()
}
}
}
}
Here is the parent class:
package com.android.forexwatch.views.timepanel
import android.animation.ValueAnimator
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.android.forexwatch.R
import com.android.forexwatch.adapters.TimePanelRecyclerViewAdapter
import com.android.forexwatch.events.TimeEvent
import com.android.forexwatch.model.CityInfoDetailed
import com.android.forexwatch.model.TimeObject
import com.android.forexwatch.utils.TimeKeeper
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
class TimePanelView(view: View, container: ViewGroup, timePanelAdapter: TimePanelRecyclerViewAdapter) : RecyclerView.ViewHolder(view) {
companion object {
private const val OffsetTime: Long = 15 * 60
private const val FrameRate: Long = 32
}
private var fxTime: TextView? = null
private var stockTime: TextView? = null
private var city: TextView? = null
private var currentTime: TextView? = null
private var fxTimeLabel: TextView? = null
private var stockTimeLabel: TextView? = null
private var carouselView: CarouselView? = null
private var carouselViewMirror: CarouselView? = null
private var parentContainer: ViewGroup? = null
private var adapter: TimePanelRecyclerViewAdapter? = null
private var cityInfo: CityInfoDetailed? = null
private var view: View? = null
init {
adapter = timePanelAdapter
this.view = view
this.parentContainer = container
EventBus.getDefault().register(this)
}
fun setupView(cityInfo: CityInfoDetailed?) {
this.cityInfo = cityInfo
city = view?.findViewById(R.id.city)
stockTime = view?.findViewById(R.id.stockTime)
fxTime = view?.findViewById(R.id.fxTime)
fxTimeLabel = view?.findViewById(R.id.fxTimeLabel)
stockTimeLabel = view?.findViewById(R.id.stockTimeLabel)
currentTime = view?.findViewById(R.id.currentTime)
carouselView = view?.findViewById(R.id.carouselLinear)
carouselViewMirror = view?.findViewById(R.id.carouselLinearMirror)
city?.text = cityInfo?.cityName
createCarousel()
}
#Subscribe
fun update(event: TimeEvent.Interval?) {
updateCurrentTime()
updateFxTime()
updateStockTime()
}
private fun createCarousel() {
carouselView?.setupView(cityInfo)
carouselViewMirror?.setupView(cityInfo, true)
carouselView?.bringToFront()
carouselViewMirror?.bringToFront()
animateCarousel()
}
private fun updateCurrentTime() {
val timezone: String? = cityInfo?.timeZone
currentTime?.text = TimeKeeper.getCurrentTimeWithTimezone(timezone, "EEE' 'HH:mm:ss")
}
private fun updateFxTime() {
updateTime(fxTime, fxTimeLabel, cityInfo?.forexOpenTimes, cityInfo?.forexCloseTimes, cityInfo?.timeZone, "FX", Companion.OffsetTime * 8)
}
private fun updateStockTime() {
updateTime(stockTime, stockTimeLabel, cityInfo?.stockOpenTime, cityInfo?.stockCloseTime, cityInfo?.timeZone, cityInfo?.stockExchangeName, Companion.OffsetTime)
}
private fun updateTime(timeView: TextView?, timeLabel: TextView?, open: TimeObject?, close: TimeObject?, timezone: String?, type: String?, timeOffset: Long) {
if (TimeKeeper.isMarketOpen(open, close, timezone)) {
timeView?.text = TimeKeeper.getTimeDifference(close, close, timezone, true)
displayMarketColors(timeView, timeOffset, close, timezone, Color.GREEN, true)
timeLabel?.text = "To " + type + " Close"
} else {
timeView?.text = TimeKeeper.getTimeDifference(open, close, timezone, false)
displayMarketColors(timeView, timeOffset, open, timezone, Color.RED, false)
timeLabel?.text = "To " + type + " Open"
}
}
private fun displayMarketColors(timeView: TextView?, timeOffset: Long, time: TimeObject?, timezone: String?, outRangeColor: Int, isMarketOpen: Boolean?) {
val color = if (TimeKeeper.isTimeWithinRange(timeOffset, time, timezone, isMarketOpen)) Color.parseColor("#FF7F00") else outRangeColor
timeView?.setTextColor(color)
}
private fun animateCarousel() {
if (cityInfo?.indexes?.count() == 1) {
return
}
// carouselView?.x = carouselView?.x?.minus(3.0F)!!
/* CoroutineScope(Dispatchers.Main).launch {
delay(FrameRate)
animateCarousel()
}*/
val animator = ValueAnimator.ofFloat(0.0f, 1.0f)
animator.repeatCount = ValueAnimator.INFINITE
animator.interpolator = LinearInterpolator()
animator.duration = 18000L
animator.addUpdateListener { animation ->
val progress = animation.animatedValue as Float
val width: Int? = carouselView?.width
val translationX = -width?.times(progress)!!
carouselView?.translationX = translationX!!
carouselViewMirror?.translationX = translationX!! + width!!
}
animator.start()
}
}
The PriceTicker layout which extends AppCompatTextView
<com.android.forexwatch.views.timepanel.PriceTicker
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
class="com.android.forexwatch.views.timepanel.PriceTicker"
android:layout_width="240dp"
android:layout_height="match_parent"
android:textAlignment="center"
android:textSize="13sp"
android:singleLine="true"
android:background="#drawable/rectangle_shape"
app:layout_constraintBottom_toBottomOf="parent"
/>
I found the solution by invalidating and requestingLayout on the priceTicker TextView and the container. Also changed the width of the container and priceTicker to the desired width.
calculatedWidth = calculatedWidth.plus(IndexWidth).toInt()
priceTicker.initialize(index, cityInfo, loader)
priceTicker.width = IndexWidth.toInt()
resetLayout(priceTicker)
}
layoutParams.width = (calculatedWidth)
resetLayout(thisContainer)
}
}
private fun destroy() {
for (idx in 0 .. this.childCount) {
val view = getChildAt(idx)
if (view is PriceTicker) {
view.destroy()
}
}
}
private fun resetLayout(view: View?) {
view?.invalidate()
view?.requestLayout()
}

Can't get agora to work. The local side gives me a black screen and the remote side never connects

I run the code in Android Studio on both my phone and an emulator at the same time.
Both have the proper permissions. I checked in the System Settings.
Here is the AgoraVideoChat class I’ve made:
package com.example.dnaire.camera
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.graphics.PorterDuff
import android.os.Bundle
import android.util.Log
import android.view.SurfaceView
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.dnaire.R
import com.example.dnaire.databinding.VideoCallingBinding
import com.example.dnaire.firebase.firebase
import com.google.firebase.database.FirebaseDatabase
import io.agora.rtc.IRtcEngineEventHandler
import io.agora.rtc.RtcEngine
import io.agora.rtc.video.VideoCanvas
import io.agora.rtc.video.VideoEncoderConfiguration
class VideoChatViewActivity: AppCompatActivity() {
lateinit var binding : VideoCallingBinding
private var mRtcEngine: RtcEngine? = null
companion object {
private val LOG_TAG = VideoChatViewActivity::class.java.simpleName
private val PERMISSION_REQ_ID_RECORD_AUDIO = 22
private val PERMISSION_REQ_ID_CAMERA = PERMISSION_REQ_ID_RECORD_AUDIO + 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = VideoCallingBinding.inflate(layoutInflater)
setContentView(binding.root)
if (checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID_RECORD_AUDIO) && checkSelfPermission(
Manifest.permission.CAMERA,
PERMISSION_REQ_ID_CAMERA
)) {
initAgoraEngineAndJoinChannel()
}
firebase()
}
fun checkSelfPermission(permission: String, requestCode: Int): Boolean {
Log.i(LOG_TAG, "checkSelfPermission $permission $requestCode")
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(permission),
requestCode
)
return false
}
return true
}
#SuppressLint("MissingSuperCall")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>, grantResults: IntArray
) {
Log.i(LOG_TAG, "onRequestPermissionsResult " + grantResults[0] + " " + requestCode)
when (requestCode) {
PERMISSION_REQ_ID_RECORD_AUDIO -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID_CAMERA)
} else {
showLongToast("No permission for " + Manifest.permission.RECORD_AUDIO)
finish()
}
}
PERMISSION_REQ_ID_CAMERA -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
initAgoraEngineAndJoinChannel()
} else {
showLongToast("No permission for " + Manifest.permission.CAMERA)
finish()
}
}
}
}
fun showLongToast(msg: String) {
this.runOnUiThread { Toast.makeText(applicationContext, msg, Toast.LENGTH_LONG).show() }
}
private fun initAgoraEngineAndJoinChannel() {
initializeAgoraEngine()
setupLocalVideo()
joinChannel()
}
private val mRtcEventHandler = object : IRtcEngineEventHandler() {
// Listen for the onFirstRemoteVideoDecoded callback.
// This callback occurs when the first video frame of a remote user is received and decoded after the remote user successfully joins the channel.
// You can call the setupRemoteVideo method in this callback to set up the remote video view.
override fun onFirstRemoteVideoDecoded(uid: Int, width: Int, height: Int, elapsed: Int) {
Log.i("testen", "VideoChatViewActivity/onFireRemoteVideoDecoded called")
runOnUiThread { setupRemoteVideo(uid) }
}
// Listen for the onUserOffline callback.
// This callback occurs when the remote user leaves the channel or drops offline.
override fun onUserOffline(uid: Int, reason: Int) {
runOnUiThread { onRemoteUserLeft() }
}
}
// Initialize the RtcEngine object.
private fun initializeAgoraEngine() {
try {
mRtcEngine = RtcEngine.create(baseContext, getString(R.string.agora_app_id), mRtcEventHandler)
} catch (e: Exception) {
Log.e(LOG_TAG, Log.getStackTraceString(e))
throw RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e))
}
}
private fun setupLocalVideo() {
mRtcEngine!!.enableVideo()
val container = binding.localVideoViewContainer
val surfaceView = RtcEngine.CreateRendererView(baseContext)
surfaceView.setZOrderMediaOverlay(true)
container.addView(surfaceView)
mRtcEngine!!.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, 0))
}
private fun joinChannel() {
Log.i("testen", "joinChannel called")
mRtcEngine!!.joinChannel(
"<credential>", "SeriChannel", "Extra Optional Data", 0) // if you do not specify the uid, we will generate the uid for you
}
private fun setupRemoteVideo(uid: Int) {
val container = binding.remoteVideoViewContainer
if (container.childCount >= 1) {
return
}
val surfaceView = RtcEngine.CreateRendererView(baseContext)
container.addView(surfaceView)
mRtcEngine!!.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_FIT, uid))
surfaceView.tag = uid // for mark purpose
val tipMsg = binding.uidText
tipMsg.visibility = View.GONE
}
private fun onRemoteUserLeft() {
val container = binding.remoteVideoViewContainer
container.removeAllViews()
val tipMsg = binding.uidText
tipMsg.visibility = View.VISIBLE
}
private fun onRemoteUserVideoMuted(uid: Int, muted: Boolean) {
val container = binding.remoteVideoViewContainer
val surfaceView = container.getChildAt(0) as SurfaceView
val tag = surfaceView.tag
if (tag != null && tag as Int == uid) {
surfaceView.visibility = if (muted) View.GONE else View.VISIBLE
}
}
override fun onDestroy() {
super.onDestroy()
leaveChannel()
RtcEngine.destroy()
mRtcEngine = null
}
// Button ClickListeners in .xml
private fun leaveChannel() {
mRtcEngine!!.leaveChannel()
}
fun onEncCallClicked(view: View) {
finish()
}
fun onSwitchCameraClicked(view: View) {
mRtcEngine!!.switchCamera()
}
fun onLocalAudioMuteClicked(view: View) {
val iv = view as ImageView
if (iv.isSelected) {
iv.isSelected = false
iv.clearColorFilter()
} else {
iv.isSelected = true
iv.setColorFilter(resources.getColor(R.color.black), PorterDuff.Mode.MULTIPLY)
}
mRtcEngine!!.muteLocalAudioStream(iv.isSelected)
}
fun onLocalVideoMuteClicked(view: View) {
val iv = view as ImageView
if (iv.isSelected) {
iv.isSelected = false
iv.clearColorFilter()
} else {
iv.isSelected = true
iv.setColorFilter(resources.getColor(R.color.black), PorterDuff.Mode.MULTIPLY)
}
mRtcEngine!!.muteLocalVideoStream(iv.isSelected)
val container = binding.localVideoViewContainer
val surfaceView = container.getChildAt(0) as SurfaceView
surfaceView.setZOrderMediaOverlay(!iv.isSelected)
surfaceView.visibility = if (iv.isSelected) View.GONE else View.VISIBLE
}
}
And here is the corresponding (and not very pretty) layout XML file:
<?xml version="1.0" encoding="UTF-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/activity_video_chat_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/remote_video_view_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="8dp"
android:layout_width="400dp"
android:layout_height="400dp"
android:background="#color/remoteBackground">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/icon_padding">
<ImageView
android:layout_width="#dimen/remote_back_icon_size"
android:layout_height="#dimen/remote_back_icon_size"
android:layout_centerInParent="true"
android:src="#drawable/icon_agora_largest" />
</RelativeLayout>
<RelativeLayout
android:id="#+id/icon_padding"
android:layout_width="match_parent"
android:layout_height="#dimen/remote_back_icon_margin_bottom"
android:layout_alignParentBottom="true" />
</FrameLayout>
<TextView
android:id="#+id/uidText"
android:layout_width="180dp"
android:layout_height="30dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="#+id/control_panel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.483"
app:layout_constraintStart_toEndOf="#+id/local_video_view_container" />
<FrameLayout
android:id="#+id/local_video_view_container"
android:layout_marginBottom="24dp"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:background="#color/localBackground"
android:onClick="onLocalContainerClick"
app:layout_constraintBottom_toTopOf="#+id/control_panel"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:layout_width="#dimen/local_back_icon_size"
android:layout_height="#dimen/local_back_icon_size"
android:layout_gravity="center"
android:scaleType="centerCrop"
android:src="#drawable/icon_agora_large" />
</FrameLayout>
<RelativeLayout
android:id="#+id/control_panel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:id="#+id/btn_call"
android:layout_width="#dimen/call_button_size"
android:layout_height="#dimen/call_button_size"
android:layout_centerInParent="true"
android:onClick="onEncCallClicked"
android:scaleType="centerCrop"
android:src="#drawable/btn_end_call" />
<ImageView
android:id="#+id/btn_switch_camera"
android:layout_width="#dimen/other_button_size"
android:layout_height="#dimen/other_button_size"
android:layout_centerVertical="true"
android:layout_marginLeft="#dimen/control_bottom_horizontal_margin"
android:layout_toEndOf="#id/btn_call"
android:layout_toRightOf="#id/btn_call"
android:onClick="onSwitchCameraClicked"
android:scaleType="centerCrop"
android:src="#drawable/btn_switch_camera" />
<ImageView
android:id="#+id/btn_mute"
android:layout_width="#dimen/other_button_size"
android:layout_height="#dimen/other_button_size"
android:layout_centerVertical="true"
android:layout_marginRight="#dimen/control_bottom_horizontal_margin"
android:layout_toStartOf="#id/btn_call"
android:layout_toLeftOf="#id/btn_call"
android:onClick="onLocalAudioMuteClicked"
android:scaleType="centerCrop"
android:src="#drawable/btn_unmute_normal" />
<ImageView
android:id="#+id/btn_video_mute"
android:layout_width="#dimen/other_button_size"
android:layout_height="#dimen/other_button_size"
android:layout_toStartOf="#id/btn_mute"
android:layout_marginEnd="8dp"
android:layout_weight="20"
android:onClick="onLocalVideoMuteClicked"
android:scaleType="centerInside"
android:src="#drawable/btn_voice" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
It starts up, and I can connect without any error messages;
the log for the activity shows me this:
2021-01-12 02:21:30.175 516-516/? I/SurfaceFlinger: Display 0 HWC layers:
type | handle | flag | format | source crop (l,t,r,b) | frame | name
------------+--------------+------+-----------+----------------------------+---------------------+------
DEVICE | 0x75196ad080 | 0002 | RGB_565 | 0.0 0.0 600.0 600.0 | 0 1176 600 1776 | SurfaceView - com.example.dnaire/com[...]ra.VideoChatViewActivity#10775c1#0#0
DEVICE | 0x74fbd49880 | 0000 | RGBA_8888 | 0.0 0.0 1080.0 2220.0 | 0 0 1080 2220 | com.example.dnaire/com.example.dnaire.camera.VideoChatViewActivity$_24379#0
DEVICE | 0x75196abf00 | 0000 | RGBA_8888 | 0.0 0.0 1080.0 72.0 | 0 0 1080 72 | StatusBar$_8073#0
DEVICE | 0x75196ad940 | 0000 | RGBA_8888 | 0.0 0.0 67.0 408.0 | 1013 328 1080 736 | com.samsung.android.app.cocktailbars[...]rservice.CocktailBarService$_13359#0
DEVICE | 0x75196ac680 | 0000 | RGBA_8888 | 0.0 0.0 1080.0 144.0 | 0 2076 1080 2220 | NavigationBar0$_8073#0
But I have no idea what that’s worth.
I added a picture of how that looks in action. It is exactly the same on both devices.
And yes, I’ve generated a token under Project > Edit > Generate Temp Token, and I enabled primary and secondary certificates.
Make sure you to check the following:
You are using the resource R.string.agora_app_id as your App ID. Make sure that you have added the correct App ID in the xml file.
In your joinChannel method, the first parameter that should be provided is your token which is supposed to be different from your App ID. For development purposes, you can do the following to generate a temporary token. When taking your application to production, make sure to setup a token server

Why can't I get my progress bar activity indicator to display longer in my Kotlin Android app

I have a custom activity which contains a progress bar activity indicator which is visible while a network access is taking place and then made invisible when it has finished the network access. The activity indicator then becomes visible again at the start of another network access and becomes invisible at the end of it. The progress bar activity indicator appears to display twice as expected but there is still a relatively long time between these two occurrences when the user is kept waiting and no activity indicator is being displayed. Are these the best locations from a UX point of view to have the progress bar activity indicator code while some task is being executed?
Here is my custom activity class with the progress bar activity indicator code:
package com.riverstonetech.gositeuk
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.location.LocationManager
import android.net.Uri
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Looper
import android.util.Log
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import com.google.android.gms.location.*
import com.google.firebase.firestore.FirebaseFirestore
import com.riverstonetech.gositeuk.Model.Site
import com.riverstonetech.gositeuk.Utilities.*
import kotlinx.android.synthetic.main.activity_county.*
class CountyActivity : AppCompatActivity() {
// Access Cloud Firestore instance
val db = FirebaseFirestore.getInstance()
lateinit var fusedLocationProviderClient: FusedLocationProviderClient
lateinit var locationRequest: LocationRequest
lateinit var locationCallback: LocationCallback
private var sites: ArrayList<Site> = ArrayList()
private var siteDistances: ArrayList<Int> = ArrayList()
private var recyclerView: RecyclerView? = null
private var adapter: SiteAdapter? = null
private var selectedCounty: String? = null
val REQUEST_CODE = 1000
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_county)
val window: Window = this.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.statusBarColor = Color.argb(255, 16, 128, 116)
}
selectedCounty = intent.getStringExtra("SUB_COUNTRY")!!
// Custom action bar code to return to list of counties
configureCustomActionBar()
if (ActivityCompat.checkSelfPermission(
this,
android.Manifest.permission.ACCESS_FINE_LOCATION
) !=
PackageManager.PERMISSION_GRANTED
) {
Log.d("Debug", "Permission not granted")
// Permission is not granted
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
android.Manifest.permission.ACCESS_FINE_LOCATION)) {
// Alert user to switch Location on in Settings
val builder = AlertDialog.Builder(this)
// Set the alert dialog title
builder.setTitle("Permission needed")
// Display a message on alert dialog
builder.setMessage("Please grant permission for \"GoSiteUK\" to use your location")
// Set a positive button and its click listener on alert dialog
builder.setPositiveButton("OK") { dialog, which ->
startActivity(Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
})
}
// Display a negative button on alert dialog
builder.setNegativeButton("Cancel") { dialog, which ->
val intent = Intent(this, CountriesActivity::class.java)
startActivity(intent)
}
// Finally, make the alert dialog using builder
val dialog: AlertDialog = builder.create()
// Display the alert dialog on app interface
dialog.show()
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_CODE
)
finish()
}
} else {
// Permission has already been granted
val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) == false) {
// Alert user to switch Location on in Settings
val builder = AlertDialog.Builder(this)
// Set the alert dialog title
builder.setTitle("Turn on \"Location\"")
// Display a message on alert dialog
builder.setMessage("\"Location\" is currently turned off. Turn on \"Location\" to enable navigation function in \"GoSiteUK\"")
// Set a positive button and its click listener on alert dialog
builder.setPositiveButton("OK") { dialog, which ->
val intent = Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
}
// Display a negative button on alert dialog
builder.setNegativeButton("Cancel") { dialog, which ->
val intent = Intent(this, CountriesActivity::class.java)
startActivity(intent)
}
// Finally, make the alert dialog using builder
val dialog: AlertDialog = builder.create()
// Display the alert dialog on app interface
dialog.show()
} else {
sites.clear()
var progressBar: ProgressBar = findViewById(R.id.countySitesProgressBar)
progressBar.visibility = ProgressBar.VISIBLE
db.collection("UKSites")
.document("England")
.collection("Counties")
.document(selectedCounty!!)
.collection(selectedCounty!!)
.get()
.addOnSuccessListener { documents ->
for (document in documents) {
val site = Site(document.data["Name"].toString())
site.address.line1 = document.data["Address Line 1"].toString()
site.address.line2 = document.data["Address Line 2"].toString()
site.address.line3 = document.data["Address Line 3"].toString()
site.address.line4 = document.data["Address Line 4"].toString()
site.address.postcode = document.data["Postcode"].toString()
site.address.phoneNumber = document.data["Telephone"].toString()
site.address.siteURL = document.data["Site URL"].toString()
site.description = document.data["Description"].toString()
site.price = document.data["Price"] as Double
site.distance = 0
site.locationCoordinate.Longitude = document.data["Longitude"] as Double
site.locationCoordinate.Latitude = document.data["Latitude"] as Double
sites.add(site)
}
progressBar.visibility = ProgressBar.GONE
BuildLocationRequest()
BuildLocationCallback()
// Create FusedProviderClient
fusedLocationProviderClient =
LocationServices.getFusedLocationProviderClient(this)
fusedLocationProviderClient.requestLocationUpdates(
locationRequest,
locationCallback,
Looper.myLooper()
)
}
.addOnFailureListener { exception ->
Log.e("Error", "Error getting documents: ", exception)
}
}
}
}
fun configureCustomActionBar() {
val actionBar: ActionBar? = this.supportActionBar
actionBar?.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM)
actionBar?.setDisplayShowCustomEnabled(true)
actionBar?.setCustomView(R.layout.sub_country_action_bar)
val countyLabel: TextView = findViewById(R.id.subCountryTextView)
countyLabel.text = selectedCounty
val view: View? = actionBar?.customView
actionBar?.setCustomView(view)
}
fun BuildLocationRequest() {
locationRequest = LocationRequest()
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
locationRequest.interval = 5000
locationRequest.fastestInterval = 3000
locationRequest.smallestDisplacement = 10f
}
fun BuildLocationCallback() {
locationCallback = object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
var progressBar: ProgressBar = findViewById(R.id.countySitesProgressBar)
val currentCoordinate = p0!!.locations.get(p0.locations.size - 1)
// Build URL for web request
var distanceAPIURL: String = BASE_URL + "&origins="
var siteLatitude: Double
var siteLongitude: Double
// add current location parameter to API URL
progressBar.visibility = ProgressBar.VISIBLE
distanceAPIURL += "${currentCoordinate.latitude},${currentCoordinate.longitude}"
// add site destinations to API URL
distanceAPIURL += "&destinations="
// Build API request from site locations
for (site in sites) {
siteLatitude = site.locationCoordinate.Latitude!!
siteLongitude = site.locationCoordinate.Longitude!!
if (site == sites.first()) {
distanceAPIURL += "${siteLatitude}%2C${siteLongitude}"
} else {
distanceAPIURL += "%7C${siteLatitude}%2C${siteLongitude}"
}
}
// Add API KEY
distanceAPIURL += GOOGLE_API_KEY
// Make web requests to Google
val distanceRequest = object :
StringRequest(Method.POST, distanceAPIURL, Response.Listener { response ->
// Parse out distances from returned data
siteDistances = parseOutDistanceValues(response)
// Update distance information for each site
for (siteIndex in 0 until sites.size) {
sites[siteIndex].distance = siteDistances[siteIndex]
}
// Sort sites in ascending order of distance
sites = sortByDistance(sites)
// Recycler View code here
recyclerView = findViewById<View>(R.id.countySitesRecyclerView) as RecyclerView
adapter = SiteAdapter(this#CountyActivity, sites)
val layoutManager = LinearLayoutManager(applicationContext)
recyclerView!!.layoutManager = layoutManager
recyclerView!!.itemAnimator = DefaultItemAnimator()
// Add a neat dividing line between items in the list
recyclerView!!.addItemDecoration(
DividerItemDecoration(
this#CountyActivity,
LinearLayoutManager.VERTICAL
)
)
recyclerView!!.addItemDecoration(
ItemOffsetDecoration(
applicationContext,
0,
0
)
)
// set the adapter
recyclerView!!.adapter = adapter
progressBar.visibility = ProgressBar.GONE
}, Response.ErrorListener { error ->
Log.d(
"ERROR",
"Could not calculate distances to sites: ${error.localizedMessage}"
)
}) {
}
Volley.newRequestQueue(applicationContext).add(distanceRequest)
}
}
}
fun previousSubCountryListButtonClicked(view: View) {
val intent: Intent = Intent(this, CountriesActivity::class.java)
startActivity(intent)
}
}
I would suggest you a few modifications.
Declare the progressbar as member of your activity and initilize it (var progressBar: ProgressBar = findViewById(R.id.countySitesProgressBar)) from onCreate
In your layout always show it by default. The only place where you need to hide it is when you send/receive response from your webservice (from what I saw you get a location and then do something with it and pass it to a webservice.
(optional) I would recommend you to use dagger or to use design patterns (like mvp/mvvm) to reuse and incapsulate your code more efficiently. If you will need to include more functionality in your activity it will be hard later on to debug it.
Edit
If you want to include also a textview as part of your loading screen you can try something like this:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appbar"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize">
<TextView
android:id="#+id/tvToolbarTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAllCaps="true"
android:textColor="#color/white"
android:textSize="#dimen/text_20"
android:textStyle="bold" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/appbar"
app:layout_constraintStart_toStartOf="parent"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/loading_layout"
android:visibility="gone"> <--------
<ProgressBar
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:id="#+id/horizontal_progressbar_indeterminate"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="#+id/user_message"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
android:gravity="center"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="Fetching Location / Fetching Data..."/>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/content1">
<TextView
tools:text="Can be recyclerview with some data"
android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:gravity="center"
android:textSize="20sp"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
The main change is that the progress bar with a text are in a separate layout instead of being part of your content. Instead of changing the visibility of the progress bar you need to change the visibility of the loading layout.

Categories

Resources