Ive been trying to get a class in my android app to be able to post snackbars. The class is a manager for a bluetooth connection, and ive needed to make it static in my main activity to achieve this. For this reason i cant send android context classes to it or store within in, making me unable to get the view needed to make a snackbar. All throughout the bluetooth service class i use the method runOnUiThread(() -> snackbarMsg to try to show snackbars. It used to work when i sent view as a parameter of the constructor, but only for the first time the main screen showed, if i switched activity and back it would stop working, and this also caused a memory leak. Any other way to solve this? Any help is appreciated.
Currently code looks like this in main activity:
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener{
//Initialise some static variables needed across the whole program
private static BluetoothService bluetoothService;
private static final String TAG = MainActivity.class.getSimpleName();
protected static ArrayList<LocationData> locations = new ArrayList<>();
protected static List<String> categories = new ArrayList<>();
Spinner spinner;
private static boolean doneFirstRun = false;
protected static LocationData selectedLocation = new LocationData();
MainActivity instance = this;
public MainActivity() {
}
/*
TODO: view in bluetooth service causes memory leak
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try{
if (!doneFirstRun){
//Getting storage
readFromFile(MainActivity.this);
//adding example location
if (locations.size()==0){
LocationData spaceRay = new LocationData();
spaceRay.setName("SpaceRay");
spaceRay.setLatitude(59.40384);
spaceRay.setLongitude(17.95228);
spaceRay.setInclination(-85);
spaceRay.setDirection(200);
spaceRay.setAltitude(99990);
locations.add(spaceRay);
}
//Checking and asking for relevant permissions
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
#SuppressLint("InlinedApi") String[] permissions = {Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN};
ActivityCompat.requestPermissions(instance, permissions, 1);
}
bluetoothService = new BluetoothService();
//starting bluetooth networking activity on new thread
startBluetoothService();
doneFirstRun = true;
}
} catch (Exception exception){
Log.e(TAG, "Failed to do first run through of code: ", exception);
}
...
protected void startBluetoothService(){
try {
Log.i(TAG, "New thread started");
bluetoothService.run(MainActivity.this);
} catch (Exception e) {
Log.e(TAG, "Bluetooth service failed: ", e);
snackbarMsg("Bluetooth service failed");
}
}
and like this is bluetooth_service class:
public class BluetoothService extends AppCompatActivity{
private static final String TAG = BluetoothService.class.getSimpleName();
private OutputStream outputStream;
private InputStream inputStream;
private BluetoothSocket socket;
private boolean writing = false;
public BluetoothService(){
}//constructor
...
//method to show snackbar message at the bottom of the screen
public Runnable snackbarMsg (String msg){
try {
View view = findViewById(R.id.btn_connect);
Snackbar snackbar = Snackbar.make(view, msg, BaseTransientBottomBar.LENGTH_SHORT);
snackbar.show();
} catch (Exception exception){
Log.e(TAG, "Could not show snackbar", exception);
}
return null;
}
Error message looks like this:
java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
at android.content.ContextWrapper.getApplicationInfo(ContextWrapper.java:190)
at android.view.ContextThemeWrapper.getTheme(ContextThemeWrapper.java:174)
at android.content.Context.obtainStyledAttributes(Context.java:809)
at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:852)
at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:819)
at androidx.appcompat.app.AppCompatDelegateImpl.findViewById(AppCompatDelegateImpl.java:640)
at androidx.appcompat.app.AppCompatActivity.findViewById(AppCompatActivity.java:261)
at antennalocator.util.BluetoothService.snackbarMsg(BluetoothService.java:204)
at antennalocator.util.BluetoothService.lambda$write$4$antennalocator-util-BluetoothService(BluetoothService.java:159)
at antennalocator.util.BluetoothService$$ExternalSyntheticLambda5.run(Unknown Source:4)
at java.lang.Thread.run(Thread.java:1012)
Managed to get around this issue. The way i solved this was by sending the view as a parameter in each method that needed to post snackbars: for example the write method:
public void write(String s, View view) {
if (!writing){
new Thread(() -> {
try {
writing = true;
outputStream.write(s.getBytes());
runOnUiThread(snackbarMsg("Sent data", view));
lockout(3000);
writing = false;
} catch (Exception exception) {
writing = false;
Log.e(TAG, "Error occurred when sending data, restarting bluetooth service", exception);
runOnUiThread(() -> snackbarMsg("Could not send data, restarting bluetooth service", view));
disconnect();
}
}).start();
} else{
runOnUiThread(snackbarMsg("Please wait a bit before sending data", view));
}
}
in main:
bluetoothService.write(ConvertToString(selectedLocation), findViewById(R.id.btn_connect));
Related
I am using the Gradle dependency implementation 'org.webrtc:google-webrtc:1.0.30039'
This is the error:
E/rtc: Fatal error in:
gen/sdk/android/generated_metrics_jni/../../../../../../../../usr/local/google/home/sakal/code/webrtc-aar-release/src/sdk/android/src/jni/jni_generator_helper.h,
line 94 last system error: 0 Check failed: !env->ExceptionCheck()
A/libc: Fatal signal 6 (SIGABRT), code -6 in tid 11556 (network_thread
), pid 11515 (est.applicatoin)
onSignalingChange: HAVE_LOCAL_OFFER is the last log call before the exception.
An SDP is created but ICE trickling is not happening i.e OnIceGathering or OnIceCandidate is not called at all.
Java Activity Code:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
DataChannel mainDataChannel;
PeerConnection mainPeerConnection;
PeerConnectionFactory factory;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initializePeerConnectionFactory();
initializeMyPeerConnection(); // Connection Initialization.
startConnection(); //Getting the offer
private void initializePeerConnectionFactory() {
PeerConnectionFactory.InitializationOptions initializationOptions =
PeerConnectionFactory.InitializationOptions.builder(this)
.setEnableInternalTracer(true)
.createInitializationOptions();
PeerConnectionFactory.initialize(initializationOptions);
PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
factory = PeerConnectionFactory.builder()
.setOptions(options)
.createPeerConnectionFactory();
}
private void startConnection() {
Log.d(TAG, "startConnection: Starting Connection...");
//CreateOffer fires the request to get ICE candidates and finish the SDP. We can listen to all these events on the corresponding observers.
mainPeerConnection.createOffer(new SimpleSdpObserver() {
#Override
public void onCreateSuccess(SessionDescription sessionDescription) {
Log.d(TAG, "onCreateSuccess: " + sessionDescription.description);
mainPeerConnection.setLocalDescription(new SimpleSdpObserver(), sessionDescription);
}
#Override
public void onCreateFailure(String s) {
Log.e(TAG, "onCreateFailure: FAILED:" + s);
}
}, new MediaConstraints());
Log.d(TAG, "startConnection: Start Connection end");
}
private void initializeMyPeerConnection() {
Log.d(TAG, "initializeMyPeerConnection: Starting Initialization...");
mainPeerConnection = createPeerConnection(factory);
mainDataChannel = mainPeerConnection.createDataChannel("sendDataChannel", new DataChannel.Init());//Setting the data channel.
mainDataChannel.registerObserver(new DataChannel.Observer() {
#Override
public void onBufferedAmountChange(long l) {
}
#Override
public void onStateChange() {
//Data channel state change
Log.d(TAG, "onStateChange: " + mainDataChannel.state().toString());
}
#Override
public void onMessage(DataChannel.Buffer buffer) {
Toast.makeText(MainActivity.this, "Got the message!", Toast.LENGTH_SHORT).show();
}
});
Log.d(TAG, "initializeMyPeerConnection: Finished Initializing.");
}
private PeerConnection createPeerConnection(PeerConnectionFactory factory) {
List<PeerConnection.IceServer> iceServers = new LinkedList<>();
iceServers.add(PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer());
PeerConnection.RTCConfiguration rtcConfiguration = new PeerConnection.RTCConfiguration(iceServers);
PeerConnection.Observer pcObserver = new MyPeerConnectionObserver(TAG, mainPeerConnection);
return factory.createPeerConnection(rtcConfiguration, pcObserver);
}
}
I was facing the same error and solved it by adding this line
android.enableDexingArtifactTransform.desugaring=false
On gradle.properties file
Try to add this permission to your manifest file:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
I'm creating my splash screen for app. While loading it executes 4 methods. First one checks if Internet permission is granted, second one sends request to API to check if it is Online, third one is getting Token from Firebase and the fourth one is checking if user is already logged-in. I'm doing it using 4 threads. Each method in case of error sets the flag as false. Then when all the threads end their work (I used .join()) The last method checks the state of flag and launch new activity or just display Error and try everything once again.
The problem I have is that I'm getting the view after all the threads finish their work. For example I have black screen, then message ("Error occured") and only after that I can see UI. But on Error the UI is refreshed, so one more time I have black screen, then result and UI for 1sec until another restart.
My question is, can I in some way stop these Threads until my UI is ready ?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
setContentView(R.layout.activity_splash);
checkProgress = findViewById(R.id.checkProgressText);
auth = FirebaseAuth.getInstance();
tokenUtils = new TokenUtils();
requestQueue = Volley.newRequestQueue(getApplicationContext());
animatedCircleLoadingView = findViewById(R.id.circle_loading_view);
//starting the animation
startLoading();
Thread[] checkers = new Thread[4];
checkers[0] = new Thread(this::checkInternetPermissions);
checkers[1] = new Thread(this::checkConnection);
checkers[2] = new Thread(this::getUserAuth);
checkers[3] = new Thread(this::getUserToken);
for (Thread t : checkers) {
try {
t.start();
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
changeActivity();
}
Check internet permission method:
private void checkInternetPermissions() {
checkProgress.setText(getString(R.string.check_internet_permissions_text));
if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET)
!= PackageManager.PERMISSION_GRANTED)
requestPermissions(new String[]{Manifest.permission.INTERNET}, 1);
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
if (requestCode != 1) {
connectionFlag = false;
}
}
Check connection method:
private void checkConnection() {
checkProgress.setText(getString(R.string.checking_api_connection));
RequestFuture<String> requestFuture = RequestFuture.newFuture();
StringRequest request = new StringRequest
(Request.Method.GET, API_CHECK,
requestFuture,
requestFuture);
requestQueue.add(request);
String response = null;
try {
response = requestFuture.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
this.connectionFlag = false;
}
if (!Objects.equals(response, "ok"))
this.connectionFlag = false;
}
Get user token method:
private void getUserToken() {
checkProgress.setText(getString(R.string.getting_user_auth_token));
String token = null;
try {
token = tokenUtils.getFirebaseToken();
} catch (ExecutionException | InterruptedException e) {
this.connectionFlag = false;
}
if (Objects.isNull(token) || Objects.requireNonNull(token).isEmpty())
this.connectionFlag = false;
}
And finally get user auth method:
private void getUserAuth() {
checkProgress.setText(getString(R.string.checking_user_auth));
authStateListener = firebaseAuth -> {
firebaseUser = firebaseAuth.getCurrentUser();
if (Objects.isNull(firebaseUser) || Objects.requireNonNull(firebaseUser.getEmail()).isEmpty()) {
this.authFlag = false;
}
};
}
Last method which handle the states of flags:
private void changeActivity() {
checkProgress.setText(getString(R.string.finalizing_text_progress));
if (connectionFlag && authFlag) {
startActivity(new Intent(SplashActivity.this, MapActivity.class));
} else if (!connectionFlag) {
Toast.makeText(getApplicationContext(), "Error occurred.", Toast.LENGTH_LONG).show();
finish();
startActivity(getIntent());
} else {
startActivity(new Intent(SplashActivity.this, LoginActivity.class));
}
}
Yes, You can try it with handler thread with some delay then it will work fine or you can start your thread on onResume() method at the time of onResume your view will have been created
I think, your way wrong. Because, API request working on asynchronous. Your app should run like this;
Check Internet connection.
API Request.
Get token in API Request onSuccess method.
Get User Auth.
I think, you shouldn't use Thread.
I just set up GCM in my Android App. But I have the problem that I don't know how to check if the device is already registered. I work with the new google play services library.
The register part looks like this:
#Override
protected String doInBackground(String... arg0) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context_app);
}
regid = gcm.register(SENDER_ID);
msg = "Dvice registered, registration ID=" + regid;
Log.d("111", msg);
sendRegistrationIdToBackend(regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
How can I modify this that it checks if the device is already registered?
Store the registration id in a databade table or shared preference and when app starting..check whether it is null or not
Google has provided very clear documentation with code.You should use following code:
// Make sure the device has the proper dependencies.
GCMRegistrar.checkDevice(this);
// Make sure the manifest was properly set - comment out this line
// while developing the app, then uncomment it when it's ready.
GCMRegistrar.checkManifest(this);
registerReceiver(mHandleMessageReceiver,
new IntentFilter(DISPLAY_MESSAGE_ACTION));
final String regId = GCMRegistrar.getRegistrationId(this);
if (regId.equals("")) {
// Automatically registers application on startup.
GCMRegistrar.register(this, SENDER_ID);
} else {
// Device is already registered on GCM, check server.
if (GCMRegistrar.isRegisteredOnServer(this)) {
// Skips registration.
mDisplay.append(getString(R.string.already_registered) + "\n");
} else {
// Try to register again, but not in the UI thread.
// It's also necessary to cancel the thread onDestroy(),
// hence the use of AsyncTask instead of a raw thread.
final Context context = this;
mRegisterTask = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
boolean registered =
ServerUtilities.register(context, regId);
// At this point all attempts to register with the app
// server failed, so we need to unregister the device
// from GCM - the app will try to register again when
// it is restarted. Note that GCM will send an
// unregistered callback upon completion, but
// GCMIntentService.onUnregistered() will ignore it.
if (!registered) {
GCMRegistrar.unregister(context);
}
return null;
}
#Override
protected void onPostExecute(Void result) {
mRegisterTask = null;
}
};
mRegisterTask.execute(null, null, null);
}
}
#Override
protected void onDestroy() {
if (mRegisterTask != null) {
mRegisterTask.cancel(true);
}
unregisterReceiver(mHandleMessageReceiver);
GCMRegistrar.onDestroy(this);
super.onDestroy();
}
private final BroadcastReceiver mHandleMessageReceiver =
new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String newMessage = intent.getExtras().getString(EXTRA_MESSAGE);
mDisplay.append(newMessage + "\n");
}
};
when you get registration Id, Store it in SharedPreferences, for example:
SharedPreferences shp = context.getSharedPreferences("anyNameYouLike",MODE_PRIVATE);
SharedPreferences.Editor editor=shp.edit();
editor.putString("RegID",registrationID).commit;
In the next time before you register check the "anyNameYouLike" if it contain field called RegID Like this:
private boolean isRegistered(Context context){
SharedPreferences shp = context.getSharedPreferences("anyNameYouLike",PRIVATE_MODE);
return shp.contains("RegID");
}
I am writing an android application to get the Facebook user albums and photos and display in my Android application.
I have created a Facebook App with APP_ID 281846961912565.
While creating the Facebook instance, I am passing this id as follows
facebook = new Facebook(APP_ID);
Using this instance, I am able to login to my FB account post on messages on facebook wall programatically.
After logging in, I get an access_token.
I'm using the access token to get the album ids using facebook.request("https://graph.facebook.com/me/albums?access_token="+facebook.getAccessToken());
Now I get {"error":{"message":"Malformed access token ACCESSTOKENACCESSTOKEN?access_token=ACCESSTOKENACCESSTOKEN","type":"OAuthException","code":190}}
Can any of you please help me resolve this issue and point out what i am doing wrong.
My code is as follows:
private static final String[] PERMISSIONS = new String[] { "publish_stream","user_photos" };
public boolean saveCredentials(Facebook facebook) {
Editor editor = getApplicationContext().getSharedPreferences(KEY,
Context.MODE_PRIVATE).edit();
editor.putString(TOKEN, facebook.getAccessToken());
editor.putLong(EXPIRES, facebook.getAccessExpires());
return editor.commit();
}
public boolean restoreCredentials(Facebook facebook) {
SharedPreferences sharedPreferences = getApplicationContext()
.getSharedPreferences(KEY, Context.MODE_PRIVATE);
facebook.setAccessToken(sharedPreferences.getString(TOKEN, null));
facebook.setAccessExpires(sharedPreferences.getLong(EXPIRES, 0));
return facebook.isSessionValid();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
facebook = new Facebook(APP_ID);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.facebook_dialog);
String facebookMessage = getIntent().getStringExtra("facebookMessage");
if (facebookMessage == null) {
facebookMessage = "Test wall post";
}
messageToPost = facebookMessage;
}
R.layout.facebook_dialog is the dialog which pops up asking if a message should be shared on facebook or not. If yes the following method is called.
public void share(View button) {
if (!facebook.isSessionValid()) {
loginAndPostToWall();
} else {
postToWall(messageToPost);
}
}
public void loginAndPostToWall() {
facebook.authorize(this, PERMISSIONS, Facebook.FORCE_DIALOG_AUTH,
new LoginDialogListener());
}
class LoginDialogListener implements DialogListener {
public void onComplete(Bundle values) {
saveCredentials(facebook);
if (messageToPost != null) {
postToWall(messageToPost);
}
}
public void onFacebookError(FacebookError error) {
showToast("Authentication with Facebook failed!");
finish();
}
public void onError(DialogError error) {
showToast("Authentication with Facebook failed!");
finish();
}
public void onCancel() {
showToast("Authentication with Facebook cancelled!");
finish();
}
}
public void postToWall(String message) {
Bundle parameters = new Bundle();
parameters.putString("message", message);
parameters.putString("description", "topic share");
try {
facebook.request("me");
String response = facebook.request("me/feed", parameters, "POST");
Log.d("Tests", "got response: " + response);
if (response == null || response.equals("")
|| response.equals("false")) {
showToast("Blank response.");
} else {
showToast("Message posted to your facebook wall!");
}
getImagesFromUserAlbum();
finish();
} catch (Exception e) {
showToast("Failed to post to wall!");
e.printStackTrace();
finish();
}
}
Later when I do a `private void getImagesFromUserAlbum() {
facebook.getAccessToken();
JSONArray albumss = null;
String response = null;
try {
response = facebook.request("me/albums");
// `
I get the error
{"error":{"message":"Malformed access token ACCESSTOKEN?access_token=ACCESSTOKEN","type":"OAuthException","code":190}}
Thanks for your help.
The code above is now the working copy. Thanks to Bartek.
If you look at the Errors page in the documentation you will see that when you get error 190 you should authorise/reauthorise the user.
I suspect that this happened to you because you first logged in, then added the permissions to access the albums to your application BUT did not log out and log back in. Hence, you need to obtain a new access token which will grant the new permissions to your application.
Please check is there &expires in your access token if yes then remove it because it is not part of access_token and try after that.
The client code for binding to a service, which is normally in the activity class; I'm trying to move it to the service class, so that the activity class would be as clean and small as possible.
i.e. basically trying to merge the code in the second box here into the first box = as much of it into the service class as possible
Single Line in Activity for Binding to Service
public class MyActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Bind to service with this line only:
AService.bindService(this);
}
}
Static bindService and ServiceConnection Moved to Service
public class AService extends Service {
public String test = "I want to see this";
public static AService aService;
private static boolean isBound;
private static Context context;
// ... IBinder, onBind etc also here on service side
public static void bindService(Context context) {
try {
Log.i(TAG, "bindService Start");
if (!isBound && context != null) {
Log.i(TAG, "Binding");
context.bindService(new Intent(context, AService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
isBound = true;
Log.i(TAG, "Bound");
}
} catch (Exception e) {
Log.e(TAG, "bindService", e);
}
}
private static ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
try {
Log.i(TAG, "onServiceConnected Start");
aService = ((AService.LocalBinder) service).getService();
if (aService != null)
Log.i(TAG, aService.test);
Log.i(TAG, "onServiceConnected Finish");
} catch (Exception e) {
Log.e(TAG, "onServiceConnected", e);
}
}
public void onServiceDisconnected(ComponentName className) {
try {
Log.i(TAG, "onServiceDisconnected");
aService = null;
} catch (Exception e) {
Log.e(TAG, "onServiceDisconnected", e);
}
}
};
public static void unbind() {
try {
Log.i(TAG, "unbind start");
if (isBound && context != null) {
Log.i(TAG, "Unbinding");
context.unbindService(serviceConnection);
isBound = false;
context = null;
Log.i(TAG, "Unbound");
}
} catch (Exception e) {
Log.e(TAG, "unbind", e);
}
}
}
But onServiceConnected is Never Called?
The log shows everything up to:
...
Bound
But NOT onServiceConnected Start or beyond
and no exceptions.
Note that when the same code was in the Activity, it works (when called with MyActivity.this)
What am I doing wrong?
Is this
AService.bindService(this);
much better than this?
bindService(new Intent(context, AService.class),
serviceConnection, Context.BIND_AUTO_CREATE);
And does ServiceConnection implementation sit in Activity really annoying your so much? I doubt that.
I don't see any point centralize everything into Service and then call a static method in the actual Service to start this Service from Activity. The best practice is to follow the standard way that Google's recommended to do things, by doing this in your way, you make your code obscure and confuse other people when reading your code (if you work in a team). It doesn't make any sense IMO.
Instead of put all your effort into isolate every single bit of service from activity, I would rather consider more on how to isolate business logic from activity and centralize them into service, and let Activity mostly focus on UI stuff.
Really hope that would help you.