This is My Code :
#RequiresApi(Build.VERSION_CODES.M)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
loadProductForTheFirst()
#RequiresApi(Build.VERSION_CODES.M)
private fun hasNetworkAvilable(context: Context): Boolean {
val service = Context.CONNECTIVITY_SERVICE
val manager = context.getSystemService(service) as ConnectivityManager
val network = manager.activeNetwork
return (network != null)
}
#RequiresApi(Build.VERSION_CODES.M)
fun loadProductForTheFirst(){
swipeRefreshMain.isRefreshing = true
viewModel.getalldata().observe(this, Observer {
if (!it.isNullOrEmpty()) {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
} else {
if (hasNetworkAvilable(this)) {
viewModel.products.observe(this, Observer {
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(it, this#HomeActivity)
swipeRefreshMain.isRefreshing = false
}
})
viewModel.setup()
} else {
/// in here if the user not internet for loading the products
/// the alert dialog displays .
AlertDialog.Builder(this)
.setTitle("Internet State")
.setMessage("please turn on your internet connection")
.create()
.show()
/// in here I want a method ( workmanager )
// that as soon as the internet be accessible
/// my product will be updated .
}
}
})
}
well , For the first time that user open my app need the internet to load product from api .
So I just want the method like WorkManager to check if the intenrnet avalibility is accessible .
And after that my method will be load from api .
I did some search but could'nt find any useful example of work with workmanager.
anyone can help me with this . ?
I did this code and work for me .
I put it here if someone looking for this method .
I used work manager to get data from api whenever the network is on .
val constraints = Constriants.builder(this)
.setRequiredNetworkType(NetworkType.Connected)
val workManager : WorkManager = WorkManager.getInstance(this)
val oneRequestWork = OneRequestWorker.build(UploadWorker::class.java)
.setconstrints(constraints)
.build
workmanager.enqueue(oneRequestWork)
the Upload worker class :
class UploadWorker(context : Context , param : WorkerParameters) : Worker(context , param)
private val viewModel: ViewModelRoom by lazy {
ViewModelProvider(
ViewModelStore(),
FactoryRoom(RepositoryCart(DataBaseRoom.invoke(applicationContext)))
)
.get(ViewModelRoom::class.java)
}
override fun dowork() : Result {
return try {
viewModel.setup()
Result.success()
} catch (e: Exception) {
Result.failure()
}
Related
I've seen that there are ways to update an app with Firebase Remote Config. Some sort of "Force Update" Notification. If anyone can explain it to me, that would be great.
How to use Firebase to update your Android App?
There are multiple ways in which you can update an Android app. The first one would be to store data in a database. Firebase has two real-time databases, Cloud Firestore and the Realtime Database. You can one or the other, according to the use case of your app. For that I recommend you check the following resource:
https://firebase.google.com/docs/database/rtdb-vs-firestore
When it comes to Remote Config, please notice that nowadays you can propagate Remote Config updates in real-time. That being said, there is no need to force anything. So I highly recommend that a look at that.
For Force update in a simple case the idea is
with firebase remort config sends the version number which you want for your application to be forced
then compare remort version with the local application version
if there is a mismatch then show a permanent dialog (cancelable=false) with a button when the user clicks on that button to open the application in the play store .
Check out this Small Class created for force update with remort config
class ForceUpdateChecker(private val context: Context, private val onUpdateNeededListener: OnUpdateNeededListener?) {
interface OnUpdateNeededListener {
fun onUpdateNeeded(updateUrl: String?)
}
fun check() {
val remoteConfig = FirebaseRemoteConfig.getInstance()
if (remoteConfig.getBoolean(KEY_UPDATE_REQUIRED)) {
val currentVersion = remoteConfig.getString(KEY_CURRENT_VERSION)
val appVersion = getAppVersion(context)
val updateUrl = remoteConfig.getString(KEY_UPDATE_URL)
if (!TextUtils.equals(currentVersion, appVersion)
&& onUpdateNeededListener != null
) {
onUpdateNeededListener.onUpdateNeeded(updateUrl)
}
}
}
private fun getAppVersion(context: Context): String {
var result = ""
try {
result = context.packageManager
.getPackageInfo(context.packageName, 0).versionName
result = result.replace("[a-zA-Z]|-".toRegex(), "")
} catch (e: PackageManager.NameNotFoundException) {
Log.e(TAG, e.message!!)
}
return result
}
class Builder(private val context: Context) {
private var onUpdateNeededListener: OnUpdateNeededListener? = null
fun onUpdateNeeded(onUpdateNeededListener: OnUpdateNeededListener?): Builder {
this.onUpdateNeededListener = onUpdateNeededListener
return this
}
fun build(): ForceUpdateChecker {
return ForceUpdateChecker(context, onUpdateNeededListener)
}
fun check(): ForceUpdateChecker {
val forceUpdateChecker = build()
forceUpdateChecker.check()
return forceUpdateChecker
}
}
companion object {
private val TAG = ForceUpdateChecker::class.java.simpleName
const val KEY_UPDATE_REQUIRED = "force_update_required"
const val KEY_CURRENT_VERSION = "force_update_current_version"
const val KEY_UPDATE_URL = "force_update_store_url"
fun with(context: Context): Builder {
return Builder(context)
}
}}
Call this like this in baseActivity (or from your landing page just not in splash screen)
ForceUpdateChecker.with(this).onUpdateNeeded(this).check();
In application on create add this
val firebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
// set in-app defaults
val remoteConfigDefaults: MutableMap<String, Any> = HashMap()
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_REQUIRED] = false
remoteConfigDefaults[ForceUpdateChecker.KEY_CURRENT_VERSION] = "1.0"
remoteConfigDefaults[ForceUpdateChecker.KEY_UPDATE_URL] =
"https://play.google.com/store/apps/details?id=com.com.classified.pems"
firebaseRemoteConfig.setDefaultsAsync(remoteConfigDefaults)
firebaseRemoteConfig.fetch(60) // fetch every minutes
.addOnCompleteListener { task ->
if (task.isSuccessful) {
Log.d(TAG, "remote config is fetched.")
firebaseRemoteConfig.fetchAndActivate()
}
}
I'm using coroutines and Set the Internet Permissions in manifest Everything But cant display my data on app when INTERNET is OFF, I'm caching my API response and successfully stored it in the database but cant retrive it when the internet is off
My Code In Repository
class HomeActivityRepository(
private val photoDatabase: PhotoDatabase,
private val applicationContext: Context
) {
private var photoLiveData = MutableLiveData<List<Photos>>()
val errorMessage = MutableLiveData<String>()
var job: Job? = null
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
onError("Exception handled: ${throwable.localizedMessage}")
}
fun getServicesAPICall(): MutableLiveData<List<Photos>> {
job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
val response = RetrofitClient.getInstance().create(ApiInterface::class.java)
val res = response.getServicesAPICall()
withContext(Dispatchers.Main) {
if (NetworkUtils.isInternetAvailable(applicationContext)) {
if (res.isSuccessful) {
photoDatabase.photoDao().insertMemes(res.body()!!)
photoLiveData.postValue(res.body())
} else {
onError("Error : ${res.message()}")
}
} else {
val photos = photoDatabase.photoDao().getPhotos()
photoLiveData.postValue(photos)
}
}
}
return photoLiveData
}
private fun onError(message: String) {
errorMessage.postValue(message)
}
}
I want to call an api multiple times using WorkManager.
where idsArrayList is a list of ids.
I send each id in the api as Path to get response and similarly for other ids.
I want the workManager to return success after it has called api for all ids.
But the problem is WorkManager only returns SUCCESS for one id from the list. This is the first time I'm using WorkManager and I tried starting work manager for every id too by iterating over idsList one by one and making instance of workManger for every id in the for loop. But I thought sending the idsList as data in the workmanager and then itering over ids from inside doWork() would be better, but it's not working like I want and I don't understand why. Here's my code:
class MyWorkManager(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
private lateinit var callGrabShifts: Call<ConfirmStatus>
override fun doWork(): Result {
val idsList = inputData.getStringArray("IDS_LIST")
val idsArrayList = idsList?.toCollection(ArrayList())
var response = ""
if (idsArrayList != null) {
try {
response = callConfirmShiftApi(idsArrayList)
if (response.contains("CONFIRM")) {
return Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
}
return Result.retry()
}
private fun callConfirmShiftApi(idsArrayList: ArrayList<String>): String {
var response = ""
for ((index, id) in idsArrayList.withIndex()) {
response = callApiForId(id)
if(index == idsArrayList.lastIndex) {
response = "CONFIRM"
}
}
return response
}
private fun callApiForId(id: String): String {
var shiftGrabStatus = ""
callGrabShifts = BaseApp.apiInterface.confirmGrabAllShifts(BaseApp.userId, id)
callGrabShifts.enqueue(object : Callback<ConfirmStatus> {
override fun onResponse(call: Call<ConfirmStatus>, response: Response<ConfirmStatus>) {
if (response.body() != null) {
shiftGrabStatus = response.body()!!.status
if (shiftGrabStatus != null) {
if (shiftGrabStatus.contains("CONFIRM")) {
val shiftNumber = ++BaseApp.noOfShiftsGrabbed
sendNotification(applicationContext)
shiftGrabStatus = "CONFIRM"
return
} else {
shiftGrabStatus = "NOT CONFIRM"
return
}
} else {
shiftGrabStatus = "NULL"
return
}
} else {
shiftGrabStatus = "NULL"
return
}
}
override fun onFailure(call: Call<ConfirmStatus>, t: Throwable) {
shiftGrabStatus = "FAILURE"
return
}
})
return shiftGrabStatus
}
}
And this is the code where I'm starting the WorkManager:
private fun confirmShiftApi(availableShiftsIdList: ArrayList<String>) {
val data = Data.Builder()
data.putStringArray("IDS_LIST", availableShiftsIdList.toArray(arrayOfNulls<String>(availableShiftsIdList.size)))
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<MyWorkManager>().setInputData(data.build())
.build()
WorkManager.getInstance(applicationContext).enqueue(oneTimeWorkRequest)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state.isFinished) {
val progress = workInfo.progress
}
Log.d("TESTING", "(MainActivity) : observing work manager - workInfo?.state - ${workInfo?.state}")
})
}
Any suggestions what I might be doing wrong or any other alternative to perform the same? I chose workmanager basicaly to perform this task even when app is closed and for learning purposes as I haven't used WorkManager before. But would switch to other options if this doesn't work.
I tried the following things:
removed the 'var response line in every method that I'm using to set the response, though I added it temporarily just for debugging earlier but it was causing an issue.
I removed the check for "CONFIRM" in doWork() method and just made the api calls, removed the extra return lines.
I tried adding manual delay in between api calls for each id.
I removed the code where I'm sending the ids data from my activity before calling workmanager and made the api call to fetch those ids inside workmanager and added more delay in between those calls to that keep running in background to check for data one round completes(to call api for all ids that were fetched earlier, it had to call api again to check for more ids on repeat)
I removed the extra api calls from onRestart() and from other conditons that were required to call api again.
I tested only one round of api calls for all ids with delay and removed the repeated call part just to test first. Didn't work.
None of the above worked, it just removed extra lines of code.
This is my final code that is tested and It cleared my doubt. Though it didn't fix this issue as the problem was because of backend server and Apis were returning failure in onResponse callback for most ids(when calls are made repeatedly using a for loop for each id) except first id and randomly last id from the list sometimes(with delay) for the rest of the ids it didn't return CONFIRM status message from api using Workmanager. Adding delay didn't make much difference.
Here's my Workmanager code:
class MyWorkManager(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
private lateinit var callGrabShifts: Call<ConfirmStatus>
override fun doWork(): Result {
val idsList = inputData.getStringArray("IDS_LIST")
val idsArrayList = idsList?.toCollection(ArrayList())
if (idsArrayList != null) {
try {
response = callConfirmShiftApi(idsArrayList)
if (response.contains("CONFIRM")) {
return Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
}
return Result.success()
}
private fun callConfirmShiftApi(idsArrayList: ArrayList<String>): String {
for ((index, id) in idsArrayList.withIndex()) {
response = callApiForId(id)
Thread.sleep(800)
if(index == idsArrayList.lastIndex) {
response = "CONFIRM"
}
}
return response
}
private fun callApiForId(id: String): String {
callGrabShifts = BaseApp.apiInterface.confirmGrabAllShifts(BaseApp.userId, id)
callGrabShifts.enqueue(object : Callback<ConfirmStatus> {
override fun onResponse(call: Call<ConfirmStatus>, response: Response<ConfirmStatus>) {
if (response.body() != null) {
shiftGrabStatus = response.body()!!.status
if (shiftGrabStatus != null) {
if (shiftGrabStatus.contains("CONFIRM")) {
return
} else {
return
}
} else {
return
}
} else {
return
}
}
override fun onFailure(call: Call<ConfirmStatus>, t: Throwable) {
return
}
})
return shiftGrabStatus
}
Eventually this problem(when an individual call is made for an id, it always returns success but when i call the api for every id using a loop, it only returns success for first call and failure for others) was solved using Service, it didn't have a complete success rate from apis either, but for 6/11 ids the api returned success(400ms delay between each api call), so it served the purpose for now.
I'm trying to create a BLE service that will scan for devices and using rxKotlin create an observable that will allow another class to observe when a device is found. I'm confused on how to create the observable that will allow another class to subscribe and tutorials are all over the place. Can someone give me a pointer on how to do so or a good tutorial.
Bluetoothservice class callback where devices are discovered
var foundDeviceObservable: Observable<BluetoothDevice> = Observable.create { }
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
with(result.device) {
var foundName = if (name == null) "N/A" else name
foundDevice = BluetoothDevice(
foundName,
address,
address,
result.device.type.toString()
)
foundDeviceObservable.subscribe {
//Update Observable value?
}
}
}
}
class DeviceListViewModel(application: Application) : AndroidViewModel(application) {
private val bluetoothService = BLEService()
//Where I am trying to do logic with device
fun getDeviceObservable(){
bluetoothService.getDeviceObservable().subscribe{ it ->
}
}
Solution
Was able to find the solution after reading user4097210's reply. Just had to change the found device to
var foundDeviceObservable: BehaviorSubject<BluetoothDevice> = BehaviorSubject.create()
and then call the next method in the callback
private val scanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
with(result.device) {
var foundName = if (name == null) "N/A" else name
foundDevice = BluetoothDevice(
foundName,
address,
address,
result.device.type.toString()
)
foundDeviceObservable.onNext(foundDevice)
}
}
}
use BehaviorSubject
// create a BehaviorSubject
var foundDeviceObservable: BehaviorSubject<BluetoothDevice> = BehaviorSubject()
// call onNext() to send new found device
foundDeviceObservable.onNext(foundDevice)
// do your logic use foundDeviceObservable
foundDeviceObservable.subscribe(...)
I have the following code for music recognition. I am using intent service to do all the music recognition in the service. I have done all the basic steps like adding all the permissions required and adding the ACRCloud android SDK in the project.
class SongIdentifyService(discoverPresenter : DiscoverPresenter? = null) : IACRCloudListener , IntentService("SongIdentifyService") {
private val callback : SongIdentificationCallback? = discoverPresenter
private val mClient : ACRCloudClient by lazy { ACRCloudClient() }
private val mConfig : ACRCloudConfig by lazy { ACRCloudConfig() }
private var initState : Boolean = false
private var mProcessing : Boolean = false
override fun onHandleIntent(intent: Intent?) {
Log.d("SongIdentifyService", "onHandeIntent called" )
setUpConfig()
addConfigToClient()
if (callback != null) {
startIdentification(callback)
}
}
public fun setUpConfig(){
Log.d("SongIdentifyService", "setupConfig called")
this.mConfig.acrcloudListener = this#SongIdentifyService
this.mConfig.host = "some-host"
this.mConfig.accessKey = "some-accesskey"
this.mConfig.accessSecret = "some-secret"
this.mConfig.protocol = ACRCloudConfig.ACRCloudNetworkProtocol.PROTOCOL_HTTP // PROTOCOL_HTTPS
this.mConfig.reqMode = ACRCloudConfig.ACRCloudRecMode.REC_MODE_REMOTE
}
// Called to start identifying/discovering the song that is currently playing
fun startIdentification(callback: SongIdentificationCallback)
{
Log.d("SongIdentifyService", "startIdentification called")
if(!initState)
{
Log.d("AcrCloudImplementation", "init error")
}
if(!mProcessing) {
mProcessing = true
if (!mClient.startRecognize()) {
mProcessing = false
Log.d("AcrCloudImplementation" , "start error")
}
}
}
// Called to stop identifying/discovering song
fun stopIdentification()
{
Log.d("SongIdentifyService", "stopIdentification called")
if(mProcessing)
{
mClient.stopRecordToRecognize()
}
mProcessing = false
}
fun cancelListeningToIdentifySong()
{
if(mProcessing)
{
mProcessing = false
mClient.cancel()
}
}
fun addConfigToClient(){
Log.d("SongIdentifyService", "addConfigToClient called")
this.initState = this.mClient.initWithConfig(this.mConfig)
if(this.initState)
{
this.mClient.startPreRecord(3000)
}
}
override fun onResult(result: String?) {
Log.d("SongIdentifyService", "onResult called")
Log.d("SongIdentifyService",result)
mClient.cancel()
mProcessing = false
val result = Gson().fromJson(result, SongIdentificationResult :: class.java)
if(result.status.code == 3000)
{
callback!!.onOfflineError()
}
else if(result.status.code == 1001)
{
callback!!.onSongNotFound()
}
else if(result.status.code == 0 )
{
callback!!.onSongFound(MusicDataMapper().convertFromDataModel(result))
//callback!!.onSongFound(Song("", "", ""))
}
else
{
callback!!.onGenericError()
}
}
override fun onVolumeChanged(p0: Double) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
interface SongIdentificationCallback {
// Called when the user is offline and music identification failed
fun onOfflineError()
// Called when a generic error occurs and music identification failed
fun onGenericError()
// Called when music identification completed but couldn't identify the song
fun onSongNotFound()
// Called when identification completed and a matching song was found
fun onSongFound(song: Song)
}
}
Now when I am starting the service I am getting the following error:
I checked the implementation of the ACRCloudClient and its extends android Activity. Also ACRCloudClient uses shared preferences(that's why I am getting a null pointer exception).
Since keeping a reference to an activity in a service is not a good Idea its best to Implement the above code in the activity. All the implementation of recognizing is being done in a separate thread anyway in the ACRCloudClient class so there is no point of creating another service for that.