I was following along a tutorial described in this article.
The code of the article can be found here: https://github.com/seamusv/event_channel_sample.
I basically do the same only that i use kotlin instead of java.
In native code (MainActivity.kt):
class MainActivity: FlutterActivity() {
private val STREAM_TAG = "alarm.eventchannel.sample/stream";
private var timerSubscription : Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
EventChannel(getFlutterView(), STREAM_TAG).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
Log.w("TAG", "adding listener")
this#MainActivity.timerSubscription = Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribe (
{
Log.w("Test", "Result we just received: $it");
events.success(1);
}, // OnSuccess
{ error -> events.error("STREAM", "Error in processing observable", error); }, // OnError
{ println("Complete"); } // OnCompletion
)
}
override fun onCancel(arguments: Any?) {
Log.w("TAG", "adding listener")
if (this#MainActivity.timerSubscription != null) {
this#MainActivity.timerSubscription?.dispose()
this#MainActivity.timerSubscription = null
}
}
}
)
}
}
In my main.dart i do the following:
int _timer = 0;
StreamSubscription _timerSubscription = null;
void _enableTimer() {
if (_timerSubscription == null) {
_timerSubscription = stream.receiveBroadcastStream().listen(_updateTimer);
}
}
void _disableTimer() {
if (_timerSubscription != null) {
_timerSubscription.cancel();
_timerSubscription = null;
}
}
void _updateTimer(timer) {
debugPrint("Timer $timer");
setState(() => _timer = timer);
}
In the build function i also create a button which then calls _enableTimer() onPressed.
new FlatButton(
child: const Text('Enable'),
onPressed: _enableTimer,
)
Whenever i now press the button to call _enableTimer() the app crashes and i get the output "Lost connection to device"...
Am i doing something wrong or is this a bug in a newer version of Flutter since the article is from December 2017?
The solution to my problem was basically to start the stream in the main thread:
class MainActivity: FlutterActivity() {
private val CHANNEL = "alarm.flutter.dev/audio"
private val STREAM_TAG = "alarm.eventchannel.sample/stream";
private var timerSubscription : Disposable? = null
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), STREAM_TAG).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
Log.w("TAG", "adding listener")
this#MainActivity.timerSubscription = Observable
.interval(1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{
Log.w("Test", "Result we just received: $it");
events.success(it);
}, // OnSuccess
{ error -> events.error("STREAM", "Error in processing observable", error); }, // OnError
{ println("Complete"); } // OnCompletion
)
}
override fun onCancel(arguments: Any?) {
Log.w("TAG", "adding listener")
if (this#MainActivity.timerSubscription != null) {
this#MainActivity.timerSubscription?.dispose()
this#MainActivity.timerSubscription = null
}
}
}
)
}
Related
i am trying to set notification callback in BLE nordic, where i am using the Android BLE library (Nordic Github). But i not ablet to get the notification event when i am changing the value of the characteristics.
`
class BleManagerHP1T(context: Context) : BleManager(context) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, GattService.TAG, message)
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
private var rxCharacteristic: BluetoothGattCharacteristic? = null
#SuppressLint("MissingPermission")
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(GattService.MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(GattService.MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
Log.d(TAG, "isRequiredServiceSupported: notify ${(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)}")
rxCharacteristic = service?.getCharacteristic(GattService.MyServiceProfile.RX_CHARACTERISTIC_UUID)
val obj = JSONObject()
obj.put("OPCODE","PROVISION")
rxCharacteristic?.value = obj.toString().encodeToByteArray()
val rxRead = gatt.writeCharacteristic(rxCharacteristic)
Log.d(TAG, "isRequiredServiceSupported: Read $rxRead")
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
enableNotifications(myCharacteristic).enqueue()
requestMtu(260).enqueue();
setNotificationCallback(myCharacteristic).with { _, data ->
Log.d(TAG, "initialize: TX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: TX char value $value")
}
}
requestMtu(260).enqueue();
enableNotifications(rxCharacteristic).enqueue()
setNotificationCallback(rxCharacteristic).with { _, data ->
Log.d(TAG, "initialize: RX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: RX char value $value")
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
override fun readCharacteristic(characteristic: BluetoothGattCharacteristic?): ReadRequest {
return Request.newReadRequest(characteristic)
}
}
`
gatt connection is establishing perfectly fine, using this code.
`
val bleManager = BleManagerHP1T(this#ControllerActivity)
synchronized (this) {
bleManager.connect(deviceMainList[position]).useAutoConnect(false).enqueue()
}
`
here is the gatt service file .
`
class GattService : Service() {
private val defaultScope = CoroutineScope(Dispatchers.Default)
private lateinit var bluetoothObserver: BroadcastReceiver
private var myCharacteristicChangedChannel: SendChannel<String>? = null
private val clientManagers = mutableMapOf<String, ClientManager>()
// val connect = BleManager()
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
// Setup as a foreground service
val notificationChannel = NotificationChannel(
GattService::class.java.simpleName,
resources.getString(R.string.gatt_service_name),
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationService =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationService.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, GattService::class.java.simpleName)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(resources.getString(R.string.gatt_service_name))
.setContentText(resources.getString(R.string.gatt_service_running_notification))
.setAutoCancel(true)
startForeground(1, notification.build())
// Observe OS state changes in BLE
bluetoothObserver = object : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val bluetoothState = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
-1
)
when (bluetoothState) {
BluetoothAdapter.STATE_ON -> enableBleServices()
BluetoothAdapter.STATE_OFF -> disableBleServices()
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Bond state changed for device ${device?.address}: ${device?.bondState}")
when (device?.bondState) {
BluetoothDevice.BOND_BONDED -> addDevice(device)
BluetoothDevice.BOND_NONE -> removeDevice(device)
}
}
}
}
}
registerReceiver(bluetoothObserver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
registerReceiver(bluetoothObserver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
// Startup BLE if we have it
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) enableBleServices()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(bluetoothObserver)
disableBleServices()
}
override fun onBind(intent: Intent?): IBinder? =
when (intent?.action) {
DATA_PLANE_ACTION -> {
DataPlane()
}
else -> null
}
override fun onUnbind(intent: Intent?): Boolean =
when (intent?.action) {
DATA_PLANE_ACTION -> {
myCharacteristicChangedChannel = null
true
}
else -> false
}
/**
* A binding to be used to interact with data of the service
*/
inner class DataPlane : Binder() {
fun setMyCharacteristicChangedChannel(sendChannel: SendChannel<String>) {
myCharacteristicChangedChannel = sendChannel
}
}
#SuppressLint("MissingPermission")
private fun enableBleServices() {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) {
Log.i(TAG, "Enabling BLE services")
bluetoothManager.adapter.bondedDevices.forEach { device -> addDevice(device) }
} else {
Log.w(TAG, "Cannot enable BLE services as either there is no Bluetooth adapter or it is disabled")
}
}
private fun disableBleServices() {
clientManagers.values.forEach { clientManager ->
clientManager.close()
}
clientManagers.clear()
}
private fun addDevice(device: BluetoothDevice) {
if (!clientManagers.containsKey(device.address)) {
val clientManager = ClientManager()
clientManager.connect(device).useAutoConnect(true).enqueue()
clientManagers[device.address] = clientManager
}
}
private fun removeDevice(device: BluetoothDevice) {
clientManagers.remove(device.address)?.close()
}
/*
* Manages the entire GATT service, declaring the services and characteristics on offer
*/
companion object {
/**
* A binding action to return a binding that can be used in relation to the service's data
*/
const val DATA_PLANE_ACTION = "data-plane"
const val TAG = "gatt-service"
}
private inner class ClientManager : BleManager(this#GattService) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, TAG, message)
Log.d(TAG, "log: $message")
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_READ != 0) &&
(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
setNotificationCallback(myCharacteristic).with { _, data ->
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
defaultScope.launch {
myCharacteristicChangedChannel?.send(value)
}
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
}
object MyServiceProfile {
val MY_SERVICE_UUID: UUID = UUID.fromString("8d67d51a-801b-43cb-aea2-bbec9d1211fd")
val MY_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51c-801b-43cb-aea2-bbec9d1211fd")
val RX_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51b-801b-43cb-aea2-bbec9d1211fd")
}
}
`
I want to call 2 retrofit services in parallel and then do an action only when both of them finished, but I don't seem to figuer it out how.
I have a viewModel where I have defined my services:
var config= List<Configuration>
fun getClientProducts() {
getClientClientConfigUseCase
.build(this)
.executeWithError({ config ->
config = config
}, {
})
}
var therapies = List<DtoTherapy>
fun getTherapies() {
getTherapiesUseCase
.build(this)
.executeWithError({ config ->
therapies = it
}, {
})
}
And then I want to call both services in parallel in my fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUi(view)
loadUserData()
viewModel.getClientProducts()
viewModel.getTherapies()
}
And when both variables config and therapies have the value do an action. But as I said maybe one service take 1 sec to respond and another 4 secs, and I want only to perfom an action when both have finished. Any help with be appreciated.
Here is the class I use to build the use case call:
abstract class SingleUseCase<T> : UseCase() {
private lateinit var single: Single<T>
private lateinit var useCaseInterface: UseCaseInterface
private var withLoader: Boolean = false
private var withErrorMessage: Boolean = false
internal abstract fun buildUseCaseSingle(): Single<T>
fun build(useCaseInterface: UseCaseInterface): SingleUseCase<T> {
this.withLoader = false
this.withErrorMessage = false
this.useCaseInterface = useCaseInterface
this.single = buildUseCaseSingle()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doAfterSuccess { useCaseInterface.onSuccess(it) }
return this
}
fun withLoader(): SingleUseCase<T> {
this.withLoader = true
return this
}
fun withErrorMessage(): SingleUseCase<T> {
this.withErrorMessage = true
return this
}
fun single(): Single<T> {
return this.single
}
fun execute(onSuccess: ((t: T) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess)
}
private fun buildObservable(onSuccess: ((t: T) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
fun executeWithError(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
useCaseInterface.onPrepareRequest(withLoader)
buildObservable(onSuccess, onError)
}
private fun buildObservable(onSuccess: ((success: T) -> Unit), onError: ((error: ApiError ) -> Unit)) {
disposeLast()
lastDisposable = single
.doFinally { useCaseInterface.onFinishRequest(this.withLoader) }
.subscribe(
{ onSuccess(it) },
{
onError(mapError(it))
useCaseInterface.onError(mapError(it), withErrorMessage)
})
lastDisposable?.let {
compositeDisposable.add(it)
}
}
private fun mapError(t: Throwable): ApiError {
return if(t is HttpException) {
val apiError = t.response()?.errorBody()?.string()
try {
ApiError (t.code(), t.response()?.errorBody()?.string(), Gson().fromJson(apiError, GenericError::class.java))
} catch(e: Exception) {
ApiError (-2, "Unkown error")
}
} else ApiError (-1, "Unkown error")
}
}
And this is a specific usecase class:
class GetClientConfigUseCase #Inject constructor(private val repository: UserRepository) :
SingleUseCase<ClientConfigResponse>() {
override fun buildUseCaseSingle(): Single<ClientConfigResponse> {
return repository.getUserConfig()
}
}
I guess you need zip operation. With zip operation you can have a result of two observable in one place when both of them received data.
Observable<List<ClientProducts>> observable1 = ...
Observable<List<DtoTherapy>> observable2 = ...
Observable.zip(observable1, observable2, new BiFunction<List<ClientProducts>, List<DtoTherapy>, Result>() {
#Override
public Result apply(List<ClientProducts> products, List<DtoTherapy> therapies) throws Exception
{
// here you have both of your data
// do operations on products and therapies
// then return the result
return result;
}
});
In MainActivity.kt, I invoke a method call "goToSecondActivity" to go to second activity. I would like to invoke method channel on the SecondActivity too but it doesn't work. The MethodCallHandler doesn't even run.
I got the MissingPluginException(No implementation found for method updatePosition on channel com.example.ble_poc/map_channel) but I have just implemented it and channel name is the same.
Can anyone tell me what should I do? I am new to Android Native.
MainActivtiy.kt:
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.ble_poc/channel"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegister.registerGeneratedPlugins(FlutterEngine(this#MainActivity))
MethodChannel(flutterEngine?.dartExecutor!!, CHANNEL).setMethodCallHandler { call, result ->
if(call.method.equals("goToSecondActivity")){
goToSecondActivity()
} else {
print("Result not implemented in MainActivity")
result.notImplemented()
}
}
}
private fun goToSecondActivity() {
startActivity(Intent(this#MainActivity, SecondActivity::class.java))
}
}
SecondActivity.kt:
class SecondActivity: FlutterActivity() {
private val CHANNEL = "com.example.ble_poc/map_channel"
private lateinit var mapView: MPIMapView
private val venueDataString: String by lazy { readFileContentFromAssets("mappedin-demo-mall.json") }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegister.registerGeneratedPlugins(FlutterEngine(this#SecondActivity))
MethodChannel(flutterEngine?.dartExecutor!!.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if(call.method.equals("updatePosition")){
println("Update position call is triggered")
val lat = call.argument<Double>("lat")
val long = call.argument<Double>("long")
val accuracy = call.argument<Double>("accuracy") ?: 7.170562529369844
val floorLevel = call.argument<Int>("floor") ?: 0
updatePosition(lat!!, long!!, accuracy, floorLevel)
} else {
print("Result not implemented in SecondActivity")
result.notImplemented()
}
}
...
main.dart
static const CHANNEL = "com.example.ble_poc/channel";
static const MAP_CHANNEL = "com.example.ble_poc/map_channel";
static const platform = MethodChannel(CHANNEL);
static const map_platform = MethodChannel(MAP_CHANNEL);
void openMap() async {
try {
platform.invokeMethod('goToSecondActivity');
Timer(const Duration(seconds: 4), () {
map_platform.invokeMethod(
"updatePosition",
{
"lat": 43.52023014,
"long": -80.5352595,
},
);
});
} on PlatformException catch (e) {
print(e.message);
}
}
...
I had Enabled the Google Android Device Verification API.
I had added the SHA-256 onto Firebase setting and updated the GSON file.
and After adding :
Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
I am getting error that SafetyNet or Captcha are not succeded (kind of error).
Can anyone tell me how can i disable the captcha check ?
Here is my code
class OTPNewActivity : AppCompatActivity(), OnKeyboardVisibilityListener, View.OnClickListener {
var TAG = "OTPNewActivity"
lateinit var binding: ActivityOtpnewBinding
val action = "android.provider.Telephony.SMS_RECEIVED"
var userEnteredCode = ""
var systemGeneratedCode = ""
var phoneNumer = ""
var phoneDigits = ""
private lateinit var auth: FirebaseAuth
private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
private var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks =
object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
Log.d(TAG, "onVerificationCompleted: $credential")
val code = credential.smsCode
if (code != null) {
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.d(TAG, "onVerificationFailed $e")
if (e is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"Invalid request"
)
} else if (e is FirebaseTooManyRequestsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"The SMS quota for the project has been exceeded $e"
)
} else {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL, "Something wents wrong"
)
}
}
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken
) {
// The SMS verification code has been sent to the provided phone number, we
// now need to ask the user to enter the code and then construct a credential
// by combining the code with a verification ID.
Log.d(TAG, "onCodeSent: $verificationId")
systemGeneratedCode = verificationId
resendToken = token
countdownTimer()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_otpnew)
initListeners()
}
private fun initListeners() {
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = false
// Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
auth = Firebase.auth
setKeyboardVisibilityListener(this)
binding.btnNext.setOnClickListener(this)
binding.tvCount.setOnClickListener(this)
binding.icBack.setOnClickListener(this)
val intent = intent
intent?.let {
phoneNumer = intent.getStringExtra(Constants.PHONE_NUMBER).toString()
phoneDigits = intent.getStringExtra(Constants.SAVE_PHONE_DIGITS).toString()
binding.textView.text =
"${this.resources.getString(R.string.digit_code)} $phoneNumer"
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumer)
.setTimeout(15L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
binding.otpView.setOtpCompletionListener(OnOtpCompletionListener { otp -> // do Stuff
userEnteredCode = otp
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
})
}
private fun verifyPhoneNumberWithCode(verificationId: String?, code: String) {
try {
val credential = PhoneAuthProvider.getCredential(verificationId!!, code)
signInWithPhoneAuthCredential(credential);
} catch (e: Exception) {
binding.otpView.setText("")
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
this#OTPNewActivity.resources.getString(R.string.wrong_Code)
)
e.printStackTrace()
}
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success")
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = true
if (phoneNumer.contains("+52")) {
LocalSharedPreference.getInstance(this).setSaveCountry("MX")
} else if (phoneNumer.contains("+92")) {
LocalSharedPreference.getInstance(this).setSaveCountry("PK")
} else if (phoneNumer.contains("+1")) {
LocalSharedPreference.getInstance(this).setSaveCountry("US")
}
LocalSharedPreference.getInstance(this).savePhoneNumber(phoneNumer)
LocalSharedPreference.getInstance(this).setPhoneDigits(phoneDigits)
val user = task.result?.user
val intent = Intent(this#OTPNewActivity, ProfileActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
finish()
} else {
// Sign in failed, display a message and update the UI
Log.w(TAG, "signInWithCredential:failure", task.exception)
if (task.exception is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"${task.exception}"
)
}
// Update UI
}
}
}
private fun setKeyboardVisibilityListener(onKeyboardVisibilityListener: OnKeyboardVisibilityListener) {
val parentView: View = (findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0)
parentView.getViewTreeObserver()
.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
private var alreadyOpen = false
private val defaultKeyboardHeightDP = 100
private val EstimatedKeyboardDP =
defaultKeyboardHeightDP + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 48 else 0
private val rect: Rect = Rect()
override fun onGlobalLayout() {
val estimatedKeyboardHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
EstimatedKeyboardDP.toFloat(),
parentView.getResources().getDisplayMetrics()
)
.toInt()
parentView.getWindowVisibleDisplayFrame(rect)
val heightDiff: Int =
parentView.getRootView().getHeight() - (rect.bottom - rect.top)
val isShown = heightDiff >= estimatedKeyboardHeight
if (isShown == alreadyOpen) {
Log.d("Keyboard state", "Ignoring global layout change...")
return
}
alreadyOpen = isShown
onKeyboardVisibilityListener.onVisibilityChanged(isShown)
}
})
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_NO) {
Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show()
} else if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_YES) {
Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show()
}
}
override fun onVisibilityChanged(visible: Boolean) {
if (!visible) {
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
}
}
override fun onResume() {
super.onResume()
binding.otpView.requestFocus()
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
registerReceiver(receiver, IntentFilter(action))
}
private fun countdownTimer() {
binding.pbNext.visibility = View.VISIBLE
binding.icNext.visibility = View.GONE
object : CountDownTimer(15000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.tvCount.setText("Resend Code in : " + millisUntilFinished / 1000)
}
override fun onFinish() {
binding.tvCount.setText("I didn`t receive a code")
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
}
}.start()
}
override fun onClick(view: View) {
when (view.id) {
R.id.btn_next -> {
if (binding.otpView.text.toString().length == 6) {
LocalSharedPreference.getInstance(this#OTPNewActivity).isPhoneNumberVerified =
true
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
}
}
R.id.tv_count -> {
if (binding.tvCount.text.equals(this#OTPNewActivity.resources.getString(R.string.i_dont_received_code)))
resendVerificationCode(phoneNumer, resendToken)
}
R.id.ic_back -> {
finish()
}
}
}
private fun resendVerificationCode(
phoneNumber: String,
token: PhoneAuthProvider.ForceResendingToken?
) {
val optionsBuilder = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumber) // Phone number to verify
.setTimeout(15L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(this) // Activity (for callback binding)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
if (token != null) {
optionsBuilder.setForceResendingToken(token) // callback's ForceResendingToken
}
PhoneAuthProvider.verifyPhoneNumber(optionsBuilder.build())
}
var receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == "android.provider.Telephony.SMS_RECEIVED") {
val bundle = intent.extras
var msgs: Array<SmsMessage?>? = null
var msg_from: String? = ""
Log.d(TAG, "onReceive called ")
if (bundle != null) {
try {
val pdus = bundle["pdus"] as Array<Any>?
msgs = arrayOfNulls(pdus!!.size)
for (i in msgs.indices) {
msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
msg_from = msgs[i]!!.getOriginatingAddress()
val msgBody: String = msgs[i]!!.getMessageBody()
if (msgBody.contains("is your verification code for running-errands.firebaseapp.com")) {
val _1: Char = msgBody[0]
val _2: Char = msgBody[1]
val _3: Char = msgBody[2]
val _4: Char = msgBody[3]
val _5: Char = msgBody[4]
val _6: Char = msgBody[5]
val code: String =
_1.toString() + _2.toString() + _3.toString() + _4.toString() + _5.toString() + _6.toString()
// binding.otpView.text = SpannableStringBuilder(code!!)
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver);
}
}
I'm trying to learn how to use RxJava in Android, but have run into a dead end. I have the following DataSource:
object DataSource {
enum class FetchStyle {
FETCH_SUCCESS,
FETCH_EMPTY,
FETCH_ERROR
}
var relay: BehaviorRelay<FetchStyle> = BehaviorRelay.createDefault(FetchStyle.FETCH_ERROR)
fun fetchData(): Observable<DataModel> {
return relay
.map { f -> loadData(f) }
}
private fun loadData(f: FetchStyle): DataModel {
Thread.sleep(5000)
return when (f) {
FetchStyle.FETCH_SUCCESS -> DataModel("Data Loaded")
FetchStyle.FETCH_EMPTY -> DataModel(null)
FetchStyle.FETCH_ERROR -> throw IllegalStateException("Error Fetching")
}
}
}
I want to trigger an update downstream, whenever I change the value of relay, but this doesn't happen. It works when the Activity is initialized, but not when I'm updating the value. Here's my ViewModel, from where I update the value:
class MainViewModel : ViewModel() {
val fetcher: Observable<UiStateModel> = DataSource.fetchData().replay(1).autoConnect()
.map { result -> UiStateModel.from(result) }
.onErrorReturn { exception -> UiStateModel.Error(exception) }
.startWith(UiStateModel.Loading())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
fun loadSuccess() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadEmpty() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_EMPTY)
}
fun loadError() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_ERROR)
}
}
This is the code from the Activity that does the subsciption:
model.fetcher
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
uiState -> mainPresenter.loadView(uiState)
})
Ended up using kotlin coroutines instead, as I was unable to re-subscribe to ConnectableObservable and start a new fetch.
Here's the code for anyone interested.
The presenter:
class MainPresenter(val view: MainView) {
private lateinit var subscription: SubscriptionReceiveChannel<UiStateModel>
fun loadSuccess(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadError(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_ERROR)
}
fun loadEmpty(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_EMPTY)
}
suspend fun subscribe(model: MainViewModel) {
subscription = model.connect()
subscription.subscribe { loadView(it) }
}
private fun loadView(uiState: UiStateModel) {
when(uiState) {
is Loading -> view.isLoading()
is Error -> view.isError(uiState.exception.localizedMessage)
is Success -> when {
uiState.result != null -> view.isSuccess(uiState.result)
else -> view.isEmpty()
}
}
}
fun unSubscribe() {
subscription.close()
}
}
inline suspend fun <E> SubscriptionReceiveChannel<E>.subscribe(action: (E) -> Unit) = consumeEach { action(it) }
The view:
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
launch(UI) {
mainPresenter.subscribe(model)
}
btn_load_success.setOnClickListener {
mainPresenter.loadSuccess(model)
}
btn_load_error.setOnClickListener {
mainPresenter.loadError(model)
}
btn_load_empty.setOnClickListener {
mainPresenter.loadEmpty(model)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("View", "onDestroy()")
mainPresenter.unSubscribe()
}
...
The model:
class MainViewModel : ViewModel() {
val TAG = this.javaClass.simpleName
private val stateChangeChannel = ConflatedBroadcastChannel<UiStateModel>()
init {
/** When the model is initialized we immediately start fetching data */
fetchData()
}
override fun onCleared() {
super.onCleared()
Log.d(TAG, "onCleared() called")
stateChangeChannel.close()
}
fun connect(): SubscriptionReceiveChannel<UiStateModel> {
return stateChangeChannel.openSubscription()
}
fun fetchData() = async {
stateChangeChannel.send(UiStateModel.Loading())
try {
val state = DataSource.loadData().await()
stateChangeChannel.send(UiStateModel.from(state))
} catch (e: Exception) {
Log.e("MainModel", "Exception happened when sending new state to channel: ${e.cause}")
}
}
internal fun loadStyle(style: DataSource.FetchStyle) {
DataSource.style = style
fetchData()
}
}
And here's a link to the project on github.