I'm building an app (android studio - java) for my several devices, the app won't be on app store.
I want to be able to update the app remotely automatically.
I figured out how to check the version against a server, however I still don't know how to automatically update the app whenever the is a new version.
Thanks a head!
Firstly, as you said, you need to check the latest version, online and locally.
Then you need to put the apk newest apk online
Finally, if the online version is ahead the local, you need to download the file and update the app.
The easiest way to do it is to use the ota_update package to download and update the app
I hope this solution is useful for you
First you have to store the latest version on the server side using API.
After that you have to compare the installed current app version to the server-side stored version if both are different and the current version lower than the server-side version at that time you have to perform the below code.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!getPackageManager().canRequestPackageInstalls()) {
startActivityForResult(new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).setData(Uri.parse(String.format("package:%s", getPackageName()))), 1234);
} else {
downloadController = new DownloadController(DashboardNewActivity.this, url);
downloadController.enqueueDownload();
}
}
Use this DownloadController.kt class
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.core.content.FileProvider
import com.app.golden.BuildConfig
import com.app.golden.R
import java.io.File
class DownloadController(private val context: Context, private val url: String) {
companion object {
private const val FILE_NAME = "company.apk"
private const val FILE_BASE_PATH = "file://"
private const val MIME_TYPE = "application/vnd.android.package-archive"
private const val PROVIDER_PATH = ".provider"
private const val APP_INSTALL_PATH = "\"application/vnd.android.package-archive\""
}
fun enqueueDownload() {
var destination = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/"
destination += FILE_NAME
val uri = Uri.parse("$FILE_BASE_PATH$destination")
val file = File(destination)
if (file.exists()) file.delete()
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadUri = Uri.parse(url)
val request = DownloadManager.Request(downloadUri)
request.setMimeType(MIME_TYPE)
request.setTitle(context.getString(R.string.app_name))
request.setDescription(context.getString(R.string.downloading))
// set destination
request.setDestinationUri(uri)
showInstallOption(destination, URI)
// Enqueue a new download and same the referenced
downloadManager.enqueue(request)
Toast.makeText(context, context.getString(R.string.downloading), Toast.LENGTH_LONG)
.show()
}
private fun showInstallOption(
destination: String,
uri: Uri
) {
// set BroadcastReceiver to install app when .apk is downloaded
val onComplete = object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val contentUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + PROVIDER_PATH,
File(destination)
)
val install = Intent(Intent.ACTION_VIEW)
install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
install.data = contentUri
context.startActivity(install)
context.unregisterReceiver(this)
} else {
val install = Intent(Intent.ACTION_VIEW)
install.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
install.setDataAndType(
uri,
APP_INSTALL_PATH
)
context.startActivity(install)
context.unregisterReceiver(this)
}
} catch (e: Exception) {
val data = e.message.toString()
Log.e("Exception: ", data)
}
}
}
context.registerReceiver(onComplete, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
}
}
Related
I need to connect my android app to an azure sql server to add to the sql server and population data from it in the app for school.
I’m not a great programmer, and don’t have much experience with Android and backend work.
Can anyone point me in the right direction on where to get started? I feel like anytime I see something for 2+ years ago the information is irrelevant.
I don’t need anything extravagant - bare bones is great.
Additionally, there seems to be more than one way to accomplish this? I see some mention jdbc and others mention rest api.
Here is the sample code for connecting SQL server from Android application.
package com.javahelps.usermanagement
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Toast
import com.javahelps.usermanagement.service.Model
import com.javahelps.usermanagement.service.ServiceFactory
import com.javahelps.usermanagement.service.UserService
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val service by lazy {
val factory = ServiceFactory.getInstance("http://10.0.2.2:8080", "admin", "admin")
factory.build(UserService::class.java)
}
private var disposable: Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun createUser(view: View) {
// Construct a user object
val user = Model.User(
etUsername.text.toString(),
etPassword.text.toString(),
etName.text.toString(),
etEmail.text.toString()
)
this.disposable = this.service.create(user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ showResult("Successfully created the new user ${user.username}") },
{ showResult("Failed to create the new user!") })
}
fun readUser(view: View) {
val username = etUsername.text.toString()
this.disposable = this.service.read(username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ user ->
run {
etUsername.setText(user.username)
etPassword.setText(user.password)
etName.setText(user.name)
etEmail.setText(user.email)
}
},
{ showResult("Failed to read the user $username") })
}
fun updateUser(view: View) {
val username = etUsername.text.toString()
// Construct a user object
val user = Model.User(
etUsername.text.toString(),
etPassword.text.toString(),
etName.text.toString(),
etEmail.text.toString()
)
this.disposable = this.service.update(username, user)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ showResult("Successfully updated the user $username") },
{ showResult("Failed to update the user $username") })
}
fun deleteUser(view: View) {
val username = etUsername.text.toString()
// Prevent from accidentally deleting the admin user
if ("admin" == username) {
showResult("Cannot delete admin!")
return
}
this.disposable = this.service.delete(username)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ showResult("Successfully deleted the user $username") },
{ showResult("Failed to delete the user $username") })
}
private fun showResult(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onPause() {
super.onPause()
disposable?.dispose()
}
}
For the required steps connecting sql server with android application follow this document.
Please guide me how to access whatsapp status folder in android 11?
I have seen status saver apps in play store that doesn't ask for any special permission but are still able to show statuses?
Tell me how can I access WhatsApp/Media/.Statuses folder in android 11?
You can solve it using the DocumentTreeIntent if you make it allow permission for the WhatsApp folder explicitly for android 11 here is how you can do that.
if (Constants.isPackageInstalled(getActivity(), "com.whatsapp")) {
Intent intent;
StorageManager sm = (StorageManager) getActivity().getSystemService(STORAGE_SERVICE);
String statusDir = getWhatsupFolder();
String str = "android.provider.extra.INITIAL_URI";
if (Build.VERSION.SDK_INT >= 29) {
intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
String scheme = ((Uri) intent.getParcelableExtra(str)).toString().replace("/root/", "/document/");
String stringBuilder = scheme +
"%3A" +
statusDir;
intent.putExtra(str, Uri.parse(stringBuilder));
} else {
intent = new Intent("android.intent.action.OPEN_DOCUMENT_TREE");
intent.putExtra(str, Uri.parse(statusDir));
}
intent.addFlags(2);
intent.addFlags(1);
intent.addFlags(128);
intent.addFlags(64);
startActivityForResult(intent, REQUEST_ACTION_OPEN_DOCUMENT_TREE);
return;
}
before using this code you must check if WhatsApp is installed or not so the first check is for that here is the code for that.
try {
context.getPackageManager().getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
after granting permission you must retrieve files via this code
private DocumentFile[] getFromSdcard() {
DocumentFile fromTreeUri = DocumentFile.fromTreeUri(requireContext().getApplicationContext(), Uri.parse(namedataprefs));
if (fromTreeUri != null && fromTreeUri.exists() && fromTreeUri.isDirectory() && fromTreeUri.canRead() && fromTreeUri.canWrite()) {
return fromTreeUri.listFiles();
}
return null;
}
DocumentFile[] allFiles = getFromSdcard();
//to get signal file path
String path = allFiles[0].getUri().toString();
you can get further details about document tree intent from the below code its just for understanding purposes
Ref : How to check which StorageVolume we have access to, and which we don't?
requestAccessButton.setOnClickListener {
storageVolumes = storageManager.storageVolumes
val primaryVolume = storageManager.primaryStorageVolume
val intent = primaryVolume.createOpenDocumentTreeIntent()
startActivityForResult(intent, 1)
}
}
private fun checkAccessToStorageVolumes() {
val storageVolumePathsWeHaveAccessTo = HashSet<String>()
val persistedUriPermissions = contentResolver.persistedUriPermissions
persistedUriPermissions.forEach {
storageVolumePathsWeHaveAccessTo.add(it.uri.toString())
}
val storageManager = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumes = storageManager.storageVolumes
for (storageVolume in storageVolumes) {
val uuid = if (storageVolume.isPrimary) {
// Primary storage doesn't get a UUID here.
"primary"
} else {
storageVolume.uuid
}
val volumeUri = uuid?.let { buildVolumeUriFromUuid(it) }
when {
uuid == null ->
Log.d("AppLog", "UUID is null for ${storageVolume.getDescription(this)}!")
storageVolumePathsWeHaveAccessTo.contains(volumeUri) ->
Log.d("AppLog", "Have access to $uuid")
else -> Log.d("AppLog", "Don't have access to $uuid")
}
}
}
private fun buildVolumeUriFromUuid(uuid: String): String {
return DocumentsContract.buildTreeDocumentUri(
"com.android.externalstorage.documents",
"$uuid:"
).toString()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
Log.d("AppLog", "resultCode:$resultCode")
val uri = data?.data ?: return
val takeFlags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
contentResolver.takePersistableUriPermission(uri, takeFlags)
Log.d("AppLog", "granted uri: ${uri.path}")
}
For Fetch Whatsapp Status in Android 11 You have to Fetch this status from this path.
which is written below:-
Android->media->com.whatsapp->WhatsApp->Media->.Statues->"You can see all statues which was show by user"
You can try this Path. it may be helpful for you.
For Android-10 and above
File(Environment.getExternalStorageDirectory() + File.separator + "Android/media/com.whatsapp/WhatsApp/Media/.Statuses")
Below Android-10 Version
File(Environment.getExternalStorageDirectory() + File.separator + "WhatsApp/Media/.Statuses")
I am making a chat app and I am trying to make message notification.
I have a Firebase database that i store all my data (users, friends, messages). When user sends a message, app adds that message to firebase database ( path is "/notification/user_who_recived/user_who_send" ).
my problem is i cant make service that gets message in "/notification..../" path and makes notification based on message.
here is my test code that doesnt work:
package com.cagsak.chatapp
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
class background_service : Service(){
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
println("background_service: THREAD İS STARTED")
val uid = FirebaseAuth.getInstance().uid ?: stopSelf()
val ref = FirebaseDatabase.getInstance().getReference("/notifications/$uid")
ref.get()
.addOnSuccessListener { data_list ->
println("background_service: MESSAGE GETTING IS SUCCESSFUL")
data_list.children.forEach { child ->
val data = child.getValue(ChatMessage::class.java)
if (data != null) {
println("background_service: MESSAGE RECEIVER IS FOUND")
SendNotification(data, baseContext)
child.ref.removeValue().addOnSuccessListener {
println("background_service: MESSAGE DATA IS DELETED")
}
}
}
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun SendNotification(message: ChatMessage, context: Context) {
FirebaseDatabase.getInstance()
.getReference("/users").get()
.addOnSuccessListener { userlist ->
userlist.children.forEach { user_data ->
val user = user_data.getValue(User::class.java)
if (user != null && user.uid == message.fromId) {
val intent = Intent(context, ChatMessage::class.java)
intent.putExtra("USERKEY", user)
val pendingIntent: PendingIntent =
PendingIntent.getActivity(context, 0, intent, 0)
NotificationCompat.Builder(context, "MY_CHANEL_ID")
.setSmallIcon(R.drawable.small_logo)
.setContentTitle(message.fromId)
.setContentText(message.text)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
}
}
}
}
}
I get a NoSuchField runtime error in my android app using Kotlin whereas the field exists
I've tried renaming as stated here No static field list_container of type I in class Landroid/support/v7/preference/R$id yet no solution
This is my MainActivityWarrant class
package com.android.example.cameraxbasic
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.android.example.cameraxbasic.helpers.PH
import com.android.example.cameraxbasic.utils.FLAGS_FULLSCREEN
import java.io.File
const val KEY_EVENT_ACTION = "key_event_action"
const val KEY_EVENT_EXTRA = "key_event_extra"
private const val IMMERSIVE_FLAG_TIMEOUT = 500L
/**
* Main entry point into our app. This app follows the single-activity pattern, and all
* functionality is implemented in the form of fragments.
*/
class MainActivityWarrant : AppCompatActivity() {
private lateinit var container: FrameLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
container = this.findViewById(R.id.fragment_contain)
Zonedata = PH.get().getString(this.applicationContext, "ZoneData", "")
Statedata = PH.get().getString(this.applicationContext, "StateData", "")
Prisondata = PH.get().getString(this.applicationContext, "PrisonData", "")
// = intent.getStringExtra("Zonedata")
// intent.getStringExtra("Statedata")
// intent.getStringExtra("Prisondata")
val currentDBPath = getDatabasePath("your_db_name").absolutePath
println()
println("$Zonedata <<<<<<<>>>>>>>>>> $Statedata <<<<<<<<>>>>>>> $Prisondata")
}
override fun onResume() {
super.onResume()
// Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
// be trying to set app to immersive mode before it's ready and the flags do not stick
container.postDelayed({
container.systemUiVisibility = FLAGS_FULLSCREEN
}, IMMERSIVE_FLAG_TIMEOUT)
}
/** When key down event is triggered, relay it via local broadcast so fragments can handle it */
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
val intent = Intent(KEY_EVENT_ACTION).apply { putExtra(KEY_EVENT_EXTRA, keyCode) }
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
true
}
else -> super.onKeyDown(keyCode, event)
}
}
companion object {
var Zonedata: String = ""
var Statedata: String = ""
var Prisondata: String = ""
/** Use external media if it is available, our app's file directory otherwise */
fun getOutputDirectory(context: Context): File {
val appContext = context.applicationContext
val mediaDir = context.externalMediaDirs.firstOrNull()?.let {
File(it, appContext.resources.getString(R.string.app_name) + "/" + Zonedata + "/" + Statedata + "/" + Prisondata).apply { mkdirs() }
}
return if (mediaDir != null && mediaDir.exists())
mediaDir else appContext.filesDir
}
//send selected prison values to be used as file name
// var ZoneNameInFull:String = Zonedata
// var StateNameInFull:String = Statedata
// var PrisonNameInFull:String = Prisondata
#SuppressLint("StaticFieldLeak")
var openCameraForNewDocument = SelectPrisonActivity()
}
// fun openCamera(view: View) {}
}
and this is the log
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.virtusync.scanningtool, PID: 21098
java.lang.NoSuchFieldError: No static field fragment_contain of type I in class Lcom/android/example/cameraxbasic/R$id; or its superclasses (declaration of 'com.android.example.cameraxbasic.R$id' appears in /data/app/com.virtusync.scanningtool-ud2au9eFKL3JmFqFoLWYtQ==/base.apk!classes2.dex)
at com.android.example.cameraxbasic.MainActivityWarrant.onCreate(MainActivityWarrant.kt:49)
at android.app.Activity.performCreate(Activity.java:7136)
at android.app.Activity.performCreate(Activity.java:7127)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2990)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3148)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1861)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6819)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)
I believe your issue is because there is no element in any of your xml that has the id of "fragment_contain". Could you double check that your id matches whichever element you are looking for? Should look something like:
<android.widget.FrameLayout
android:id="#+id/fragment_contain"
android:layout_width="match_parent"
android:layout_height="match_parent" />
I'm using UrbanAirship for push notifications. Everything is working fine, but there are certain scenarios where I wouldn't like to display the push notification coming from urban. Do I have to handle this on the server side or can I handle it on the client side?
If possible, how would I handle this on the client side? I've tried canceling the notification, and that works, but it still displays the rollover message in the status bar.
You need to override Airship's notification factory for this:
package your.package;
import android.app.Notification;
import android.content.Context;
import android.support.annotation.NonNull;
import com.urbanairship.push.PushMessage;
import com.urbanairship.push.notifications.DefaultNotificationFactory;
public class PushNotificationFactory extends DefaultNotificationFactory {
public PushNotificationFactory(Context context) {
super(context);
}
public Notification createNotification(#NonNull PushMessage message, int notificationId) {
boolean shouldWeShowNotification = false; // your condition goes here
if (shouldWeShowNotification) {
return super.createNotification(message, notificationId);
} else {
return null;
}
}
}
and when taking off:
UAirship.takeOff(this, new UAirship.OnReadyCallback() {
#Override
public void onAirshipReady(UAirship airship) {
NotificationFactory notificationFactory = new PushNotificationFactory(getApplicationContext());
airship.getPushManager().setNotificationFactory(notificationFactory);
}
});
You need to create a BroadcastReceiver in your app to intercept the push. You can then choose to display a custom notification or not.
Check out the setup docs http://docs.urbanairship.com/build/android_features.html#set-up
If you're using Kotlin:
class PushNotificationFactory(context: Context) : NotificationFactory(context) {
override fun createNotification(message: PushMessage, notificationId: Int): Notification? {
val notificationWillBeShowed = false // your condition goes here
return if (notificationWillBeShowed) {
// Show notification
super.createNotification(message, notificationId)
} else {
// Prevent notification from showing
null
}
}
}
class UrbanAirshipAutopilot : Autopilot() {
#RequiresApi(Build.VERSION_CODES.O)
override fun onAirshipReady(airship: UAirship) {
airship.pushManager.userNotificationsEnabled = true
val context = UAirship.getApplicationContext()
val notificationFactory = PushNotificationFactory(context)
airship.pushManager.notificationFactory = notificationFactory
if (Build.VERSION.SDK_INT >= 26) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val defaultChannelId = context.getString(R.string.notification_channel_id_default)
val defaultChannelName = context.getString(R.string.notification_channel_name_default)
val channel = NotificationChannel(defaultChannelId, defaultChannelName, NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
}
override fun createAirshipConfigOptions(context: Context): AirshipConfigOptions? {
val defaultChannelId = context.getString(R.string.notification_channel_id_default)
return AirshipConfigOptions.Builder()
.setDevelopmentAppKey(BuildConfig.URBAN_AIRSHIP_APP_KEY_DEVELOPMENT)
.setDevelopmentAppSecret(BuildConfig.URBAN_AIRSHIP_APP_SECRET_DEVELOPMENT)
.setProductionAppKey(BuildConfig.URBAN_AIRSHIP_APP_KEY_PRODUCTION)
.setProductionAppSecret(BuildConfig.URBAN_AIRSHIP_APP_SECRET_PRODUCTION)
.setInProduction(!BuildConfig.DEBUG)
.setGcmSender(BuildConfig.CLOUD_MESSAGING_SENDER_ID) // FCM/GCM sender ID
// .setNotificationIcon(R.drawable.ic_notification)
// .setNotificationAccentColor(ContextCompat(getContext(), R.color.accent))
.setNotificationChannel(defaultChannelId)
.build()
}
}