Can u provide me the simple code for scanning the nearby BLE devices and list it by device name and MAC ID. I tried this using sample code provided in http://developer.android.com/guide/topics/connectivity/bluetooth-le.html. But didn't work, any reference link or ideas since i am new to BLE app.
This example is based on the developers web you posted and works great for me. This is the code:
DeviceScanActivity.class
package com.example.android.bluetoothlegatt;
import android.app.Activity;
import android.app.ListActivity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class DeviceScanActivity extends ListActivity {
private LeDeviceListAdapter mLeDeviceListAdapter;
private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler;
private static final int REQUEST_ENABLE_BT = 1;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setTitle(R.string.title_devices);
mHandler = new Handler();
// Use this check to determine whether BLE is supported on the device. Then you can
// selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Checks if Bluetooth is supported on the device.
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
if (!mScanning) {
menu.findItem(R.id.menu_stop).setVisible(false);
menu.findItem(R.id.menu_scan).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
} else {
menu.findItem(R.id.menu_stop).setVisible(true);
menu.findItem(R.id.menu_scan).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(
R.layout.actionbar_indeterminate_progress);
}
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
mLeDeviceListAdapter.clear();
scanLeDevice(true);
break;
case R.id.menu_stop:
scanLeDevice(false);
break;
}
return true;
}
#Override
protected void onResume() {
super.onResume();
// Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled,
// fire an intent to display a dialog asking the user to grant permission to enable it.
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
// Initializes list view adapter.
mLeDeviceListAdapter = new LeDeviceListAdapter();
setListAdapter(mLeDeviceListAdapter);
scanLeDevice(true);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
#Override
protected void onPause() {
super.onPause();
scanLeDevice(false);
mLeDeviceListAdapter.clear();
}
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
invalidateOptionsMenu();
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
invalidateOptionsMenu();
}
// Adapter for holding devices found through scanning.
private class LeDeviceListAdapter extends BaseAdapter {
private ArrayList<BluetoothDevice> mLeDevices;
private LayoutInflater mInflator;
public LeDeviceListAdapter() {
super();
mLeDevices = new ArrayList<BluetoothDevice>();
mInflator = DeviceScanActivity.this.getLayoutInflater();
}
public void addDevice(BluetoothDevice device) {
if(!mLeDevices.contains(device)) {
mLeDevices.add(device);
}
}
public BluetoothDevice getDevice(int position) {
return mLeDevices.get(position);
}
public void clear() {
mLeDevices.clear();
}
#Override
public int getCount() {
return mLeDevices.size();
}
#Override
public Object getItem(int i) {
return mLeDevices.get(i);
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder viewHolder;
// General ListView optimization code.
if (view == null) {
view = mInflator.inflate(R.layout.listitem_device, null);
viewHolder = new ViewHolder();
viewHolder.deviceAddress = (TextView) view.findViewById(R.id.device_address);
viewHolder.deviceName = (TextView) view.findViewById(R.id.device_name);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
BluetoothDevice device = mLeDevices.get(i);
final String deviceName = device.getName();
if (deviceName != null && deviceName.length() > 0)
viewHolder.deviceName.setText(deviceName);
else
viewHolder.deviceName.setText(R.string.unknown_device);
viewHolder.deviceAddress.setText(device.getAddress());
return view;
}
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
#Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mLeDeviceListAdapter.addDevice(device);
mLeDeviceListAdapter.notifyDataSetChanged();
}
});
}
};
static class ViewHolder {
TextView deviceName;
TextView deviceAddress;
}
}
The custom layout for the Listview listitem_device.xml :
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:id="#+id/device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24dp"/>
<TextView android:id="#+id/device_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12dp"/>
</LinearLayout>
The progress bar on scanning actionbar_indeterminate_progress.xml :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="wrap_content"
android:layout_width="56dp"
android:minWidth="56dp">
<ProgressBar android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"/>
</FrameLayout>
The menu layout main.xml :
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+id/menu_refresh"
android:checkable="false"
android:orderInCategory="1"
android:showAsAction="ifRoom"/>
<item android:id="#+id/menu_scan"
android:title="#string/menu_scan"
android:orderInCategory="100"
android:showAsAction="ifRoom|withText"/>
<item android:id="#+id/menu_stop"
android:title="#string/menu_stop"
android:orderInCategory="101"
android:showAsAction="ifRoom|withText"/>
</menu>
The strings layout strings.xml :
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ble_not_supported">BLE is not supported</string>
<string name="error_bluetooth_not_supported">Bluetooth not supported.</string>
<string name="unknown_device">Unknown device</string>
<!-- Menu items -->
<string name="menu_connect">Connect</string>
<string name="menu_disconnect">Disconnect</string>
<string name="menu_scan">Scan</string>
<string name="menu_stop">Stop</string>
</resources>
And the manifest AndroidManifest.xml :
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.bluetoothlegatt"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="18"
android:targetSdkVersion="19"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<application android:label="#string/app_name"
android:icon="#drawable/ic_launcher"
android:theme="#android:style/Theme.Holo.Light">
<activity android:name=".DeviceScanActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
I think this is all. If I missed anything let me now and I fix it. Hope it helps!!
(Bluetooth LE quite sucks in Android yet :D... needs and update fast!)
UPDATE:
Check the full example of BLE scan and connection in GitHub:
https://github.com/kodartcha/BLEDeviceScanSample
this is quite old question, but for future readers I just want to propose to check out official source codes provided by Bluetooth SIG:
Application Accelerator
There are small, easy to understand and documented apps for most mobile platforms (Android, iOS, Windows Phone and more) + some materials/tutorials. If you want to start playing with BLE this is the best starting point in my opinion.
Everything is free, but you need to register on the website. As far as I remember there is maybe 1-3 emails by year, all connected with new tools for Bluetooth development.
Darek
Related
This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 3 years ago.
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.spammessagedetector, PID: 2137
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.spammessagedetector/com.example.spammessagedetector.Inbox}:
java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.swiperefreshlayout.widget.SwipeRefreshLayout.setOnRefreshListener(androidx.swiperefreshlayout.widget.SwipeRefreshLayout$OnRefreshListener)'
on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void
androidx.swiperefreshlayout.widget.SwipeRefreshLayout.setOnRefreshListener(androidx.swiperefreshlayout.widget.SwipeRefreshLayout$OnRefreshListener)'
on a null object reference
at com.example.spammessagedetector.Inbox.onCreate(Inbox.java:76)
at android.app.Activity.performCreate(Activity.java:6662)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
here is the code for Inbox.java
package com.example.spammessagedetector;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Bundle;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import com.example.spammessagedetector.R;
import com.example.spammessagedetector.adapter.MessagesAdapter;
import com.example.spammessagedetector.helper.DividerItemDecoration;
import com.example.spammessagedetector.model.Message;
import com.example.spammessagedetector.network.ApiClient;
import com.example.spammessagedetector.network.ApiInterface;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class Inbox extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener, MessagesAdapter.MessageAdapterListener{
private List<Message> messages = new ArrayList<>();
private RecyclerView recyclerView;
private MessagesAdapter mAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private ActionModeCallback actionModeCallback;
private ActionMode actionMode;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_inbox);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
if (fab != null)
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
swipeRefreshLayout.setOnRefreshListener(this);
mAdapter = new MessagesAdapter(this, messages, this);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
recyclerView.setAdapter(mAdapter);
actionModeCallback = new ActionModeCallback();
// show loader and fetch messages
swipeRefreshLayout.post(
new Runnable() {
#Override
public void run() {
getInbox();
}
}
);
}
/**
* Fetches mail messages by making HTTP request
* url: https://api.androidhive.info/json/inbox.json
*/
private void getInbox() {
swipeRefreshLayout.setRefreshing(true);
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<List<Message>> call = apiService.getInbox();
call.enqueue(new Callback<List<Message>>() {
#Override
public void onResponse(Call<List<Message>> call, Response<List<Message>> response) {
// clear the inbox
messages.clear();
// add all the messages
// messages.addAll(response.body());
// TODO - avoid looping
// the loop was performed to add colors to each message
for (Message message : response.body()) {
// generate a random color
message.setColor(getRandomMaterialColor("400"));
messages.add(message);
}
mAdapter.notifyDataSetChanged();
swipeRefreshLayout.setRefreshing(false);
}
#Override
public void onFailure(Call<List<Message>> call, Throwable t) {
Toast.makeText(getApplicationContext(), "Unable to fetch json: " + t.getMessage(), Toast.LENGTH_LONG).show();
swipeRefreshLayout.setRefreshing(false);
}
});
}
/**
* chooses a random color from array.xml
*/
private int getRandomMaterialColor(String typeColor) {
int returnColor = Color.GRAY;
int arrayId = getResources().getIdentifier("mdcolor_" + typeColor, "array", getPackageName());
if (arrayId != 0) {
TypedArray colors = getResources().obtainTypedArray(arrayId);
int index = (int) (Math.random() * colors.length());
returnColor = colors.getColor(index, Color.GRAY);
colors.recycle();
}
return returnColor;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_search) {
Toast.makeText(getApplicationContext(), "Search...", Toast.LENGTH_SHORT).show();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onRefresh() {
// swipe refresh is performed, fetch the messages again
getInbox();
}
#Override
public void onIconClicked(int position) {
if (actionMode == null) {
actionMode = startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
}
#Override
public void onIconImportantClicked(int position) {
// Star icon is clicked,
// mark the message as important
Message message = messages.get(position);
message.setImportant(!message.isImportant());
messages.set(position, message);
mAdapter.notifyDataSetChanged();
}
#Override
public void onMessageRowClicked(int position) {
// verify whether action mode is enabled or not
// if enabled, change the row state to activated
if (mAdapter.getSelectedItemCount() > 0) {
enableActionMode(position);
} else {
// read the message which removes bold from the row
Message message = messages.get(position);
message.setRead(true);
messages.set(position, message);
mAdapter.notifyDataSetChanged();
Toast.makeText(getApplicationContext(), "Read: " + message.getMessage(), Toast.LENGTH_SHORT).show();
}
}
#Override
public void onRowLongClicked(int position) {
// long press is performed, enable action mode
enableActionMode(position);
}
private void enableActionMode(int position) {
if (actionMode == null) {
actionMode = startSupportActionMode(actionModeCallback);
}
toggleSelection(position);
}
private void toggleSelection(int position) {
mAdapter.toggleSelection(position);
int count = mAdapter.getSelectedItemCount();
if (count == 0) {
actionMode.finish();
} else {
actionMode.setTitle(String.valueOf(count));
actionMode.invalidate();
}
}
private class ActionModeCallback implements ActionMode.Callback {
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.menu_action_mode, menu);
// disable swipe refresh if action mode is enabled
swipeRefreshLayout.setEnabled(false);
return true;
}
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.action_delete:
// delete all the selected messages
deleteMessages();
mode.finish();
return true;
default:
return false;
}
}
#Override
public void onDestroyActionMode(ActionMode mode) {
mAdapter.clearSelections();
swipeRefreshLayout.setEnabled(true);
actionMode = null;
recyclerView.post(new Runnable() {
#Override
public void run() {
mAdapter.resetAnimationIndex();
// mAdapter.notifyDataSetChanged();
}
});
}
}
// deleting the messages from recycler view
private void deleteMessages() {
mAdapter.resetAnimationIndex();
List<Integer> selectedItemPositions =
mAdapter.getSelectedItems();
for (int i = selectedItemPositions.size() - 1; i >= 0; i--) {
mAdapter.removeData(selectedItemPositions.get(i));
}
mAdapter.notifyDataSetChanged();
}
}
activity_inbox.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:mContext=".Inbox">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</com.google.android.material.appbar.AppBarLayout>
<include layout="#layout/content_main"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:onClick="floatbuttonclick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:backgroundTint="#color/colorPrimary"
app:srcCompat="#drawable/ic_edit_white_24dp"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.spammessagedetector">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme.NoActionBar">
<!-- <activity android:name=".Inbox"></activity> -->
<activity android:name=".MainActivity" />
<activity android:name=".Profile" />
<activity android:name=".Splash" />
<activity android:name=".Inbox">
<!-- android:theme="#style/splash" -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
error at this line.Your referenced id not found on current layout.
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
why call same method multiple time in your activity.may be here miss lead your swipe_refresh_layout id.
setContentView(R.layout.activity_inbox);
setContentView(R.layout.activity_main);
by adding internet permission and removing setContentView(R.layout.activity_main); the code gets executed successfully.
I have a navigation drawer with some options (Home, Bluetooth, Alarm, Tips). I linked an activity to Bluetooth and Alarm but I got some problems with Home.
When I run the application I have a splash screen which is connected to the Home.java, in this activity I can click on my drawer and select the others alarm.java anche bluetooth.java but, when I link an activity to home, using the same method, my app stops working.
Thank you so much!
This is my manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.progetto.app">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity
android:name=".SplashScreen"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Home"
android:label="#string/Home"
android:theme="#style/AppTheme.NoActionBar" />
<activity
android:name=".Alarm"
android:label="#string/title_activity_alarm"
android:theme="#style/AppTheme.NoActionBar" />
<activity
android:name=".Bluetooth"
android:label="#string/title_activity_bt"
android:theme="#style/AppTheme.NoActionBar" />
<activity
android:name=".Tips"
android:label="#string/title_activity_tips"
android:theme="#style/AppTheme.NoActionBar" />
<activity android:name=".AlarmActivity" />
<receiver android:name=".Alarm_Receiver" />
<activity android:name=".DeviceListActivity" />
<activity android:name=".ArduinoMain"/>
<service
android:name=".RingtonePlayingService"
android:enabled="true" />
</application>
</manifest>
This is my Home.java with drawer navigation
package com.progetto.app;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
public class Home extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
DrawerLayout drawer;
NavigationView navigationView;
Toolbar toolbar = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
}
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.home, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
switch (id){
case R.id.nav_home:
Intent h = new Intent(Home.this,ArduinoMain.class);
startActivity(h);
break;
case R.id.nav_bt:
Intent i = new Intent(Home.this,DeviceListActivity.class);
startActivity(i);
break;
case R.id.nav_alarm:
Intent g = new Intent(Home.this,AlarmActivity.class);
startActivity(g);
break;
case R.id.nav_tips:
Intent s = new Intent(Home.this,Tips.class);
startActivity(s);
break;
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
}
This is the activity I'm trying to connect to Home, I want to see this activity when the spash screen is over
package com.progetto.app;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class ArduinoMain extends Activity {
//Declare buttons & editText
Button functionOne, functionTwo;
private EditText editText;
//Memeber Fields
private BluetoothAdapter btAdapter = null;
private BluetoothSocket btSocket = null;
private OutputStream outStream = null;
// UUID service - This is the type of Bluetooth device that the BT module is
// It is very likely yours will be the same, if not google UUID for your manufacturer
private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
// MAC-address of Bluetooth module
public String newAddress = null;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_arduino_main);
addKeyListener();
//Initialising buttons in the view
//mDetect = (Button) findViewById(R.id.mDetect);
functionOne = (Button) findViewById(R.id.functionOne);
functionTwo = (Button) findViewById(R.id.functionTwo);
//getting the bluetooth adapter value and calling checkBTstate function
btAdapter = BluetoothAdapter.getDefaultAdapter();
checkBTState();
/**************************************************************************************************************************8
* Buttons are set up with onclick listeners so when pressed a method is called
* In this case send data is called with a value and a toast is made
* to give visual feedback of the selection made
*/
functionOne.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendData("1");
Toast.makeText(getBaseContext(), "Function 1", Toast.LENGTH_SHORT).show();
}
});
functionTwo.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
sendData("2");
Toast.makeText(getBaseContext(), "Function 2", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onResume() {
super.onResume();
// connection methods are best here in case program goes into the background etc
//Get MAC address from DeviceListActivity
Intent intent = getIntent();
newAddress = intent.getStringExtra(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// Set up a pointer to the remote device using its address.
BluetoothDevice device = btAdapter.getRemoteDevice(newAddress);
//Attempt to create a bluetooth socket for comms
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
} catch (IOException e1) {
Toast.makeText(getBaseContext(), "ERROR - Could not create Bluetooth socket", Toast.LENGTH_SHORT).show();
}
// Establish the connection.
try {
btSocket.connect();
} catch (IOException e) {
try {
btSocket.close(); //If IO exception occurs attempt to close socket
} catch (IOException e2) {
Toast.makeText(getBaseContext(), "ERROR - Could not close Bluetooth socket", Toast.LENGTH_SHORT).show();
}
}
// Create a data stream so we can talk to the device
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Toast.makeText(getBaseContext(), "ERROR - Could not create bluetooth outstream", Toast.LENGTH_SHORT).show();
}
//When activity is resumed, attempt to send a piece of junk data ('x') so that it will fail if not connected
// i.e don't wait for a user to press button to recognise connection failure
sendData("x");
}
#Override
public void onPause() {
super.onPause();
//Pausing can be the end of an app if the device kills it or the user doesn't open it again
//close all connections so resources are not wasted
//Close BT socket to device
try {
btSocket.close();
} catch (IOException e2) {
Toast.makeText(getBaseContext(), "ERROR - Failed to close Bluetooth socket", Toast.LENGTH_SHORT).show();
}
}
//takes the UUID and creates a comms socket
private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException {
return device.createRfcommSocketToServiceRecord(MY_UUID);
}
//same as in device list activity
private void checkBTState() {
// Check device has Bluetooth and that it is turned on
if(btAdapter==null) {
Toast.makeText(getBaseContext(), "ERROR - Device does not support bluetooth", Toast.LENGTH_SHORT).show();
finish();
} else {
if (btAdapter.isEnabled()) {
} else {
//Prompt user to turn on Bluetooth
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, 1);
}
}
}
// Method to send data
private void sendData(String message) {
byte[] msgBuffer = message.getBytes();
try {
//attempt to place data on the outstream to the BT device
outStream.write(msgBuffer);
} catch (IOException e) {
//if the sending fails this is most likely because device is no longer there
Toast.makeText(getBaseContext(), "ERROR - Device not found", Toast.LENGTH_SHORT).show();
finish();
}
}
public void addKeyListener() {
// get edittext component
editText = (EditText) findViewById(R.id.editText1);
// add a keylistener to keep track user input
editText.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
// if keydown and send is pressed implement the sendData method
if ((keyCode == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) {
//I have put the * in automatically so it is no longer needed when entering text
sendData('*' + editText.getText().toString());
Toast.makeText(getBaseContext(), "Sending text", Toast.LENGTH_SHORT).show();
return true;
}
return false;
}
});
}
}
This is the splash screen
package com.progetto.app;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
public class SplashScreen extends AppCompatActivity {
private ImageView iv;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash_screen);
iv=(ImageView) findViewById(R.id.iv);
Animation myanim= AnimationUtils.loadAnimation(this,R.anim.mytransition);
iv.startAnimation(myanim);
final Intent i=new Intent(this, Home.class);
Thread timer =new Thread(){
public void run(){
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
startActivity(i);
finish();
}
}
};
timer.start();
}
}
This is the project
DropBox
This is the apk
Apk
I am working on a simple android app to do Internet-of-Things functions using the WunderBar Relayr toolkit to have sensors send data back to the android app so as to display the data. I am trying to emulate the sample temperature display app as shown in the wunderbar relayr website as well as the relayr GitHub repository so as to understand how the sensor and app communicate.
I am able to activate the master module as well as the humidity/temperature sensor, but even though the sensors work fine, the readings in the app is shown to be constant for example: 22*C. My app does not seem to be collecting the data
I can log in using my credentials, but then instead of my name being displayed, it displays another name.How can I resolve this issue?
The code is shown below.
ThermometerDemoActivity.java:
package com.vasansdomain.pavan.thermometer;
//import the Android classes we will need
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
//import the relayr SDK
import io.relayr.RelayrSdk;
import io.relayr.model.DeviceModel;
import io.relayr.model.Reading;
import io.relayr.model.Transmitter;
import io.relayr.model.TransmitterDevice;
import io.relayr.model.User;
import rx.Observable;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
public class ThermometerDemoActivity extends ActionBarActivity
{
private TextView mWelcomeTextView;
private TextView mTemperatureValueTextView;
private TextView mTemperatureNameTextView;
private TransmitterDevice mDevice;
private Subscription mUserInfoSubscription = Subscriptions.empty();
private Subscription mTemperatureDeviceSubscription = Subscriptions.empty();
/**
* Once the Activity has been started, the onCreate method will be called
* #param savedInstanceState
*/
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
View view = View.inflate(this, R.layout.activity_thermometer_demo, null);
mWelcomeTextView = (TextView) view.findViewById(R.id.txt_welcome);
mTemperatureValueTextView = (TextView) view.findViewById(R.id.txt_temperature_value);
mTemperatureNameTextView = (TextView) view.findViewById(R.id.txt_temperature_name);
setContentView(view);
//we use the relayr SDK to see if the user is logged in by caling the isUserLoggedIn function
if (RelayrSdk.isUserLoggedIn())
{
updateUiForALoggedInUser();
}
else
{
updateUiForANonLoggedInUser();
logIn();
}
}
/**
* When Android is ready to draw any menus it initiates the
* "prepareOptionsMenu" event, this method is caled to handle that
* event.
* #param menu
* #return
*/
#Override
public boolean onPrepareOptionsMenu(Menu menu)
{
//remove any previous items from the menu
menu.clear();
//Check to see if the user is logged in
if (RelayrSdk.isUserLoggedIn())
{
//if the user is logged in, we ask Android to draw the menu
//we defined earlier in the thermometer_demo_logged_in.xml
//file
getMenuInflater().inflate(R.menu.thermometer_demo_logged_in, menu);
}
else
{
//otherwise we return the
//thermometer_demo_not_logged_in.xml file
getMenuInflater().inflate(R.menu.thermometer_demo_not_logged_in, menu);
}
//we must return this, so that any other classes interested in
//the prepare menu event can do something.
return super.onPrepareOptionsMenu(menu);
}
/**
* When a menu item is selected, we see which item was called and
* decide what to do according to the item.
*/
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
//if the user selected login
if (item.getItemId() == R.id.action_log_in)
{
//we call the login method on the relayr SDK
logIn();
return true;
}
else if (item.getItemId() == R.id.action_log_out)
{
//otherwise we call the logout method defined later in this class
logOut();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* The LogIn method definition
*/
private void logIn() {
RelayrSdk.logIn(this).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<User>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Toast.makeText(ThermometerDemoActivity.this, R.string.unsuccessfully_logged_in, Toast.LENGTH_SHORT).show();
updateUiForANonLoggedInUser();
}
#Override
public void onNext(User user) {
Toast.makeText(ThermometerDemoActivity.this, R.string.successfully_logged_in, Toast.LENGTH_SHORT).show();
invalidateOptionsMenu();
updateUiForALoggedInUser();
}
});
}
/**
* Called when the user logs out
*/
private void logOut()
{
unSubscribeToUpdates();
//call the logOut method on the relayr SDK
RelayrSdk.logOut();
//call the invalidateOptionsMenu this is defined in the
//Activity class and is used to reset the menu option
invalidateOptionsMenu();
//use the Toast library to display a message to the user
Toast.makeText(this, R.string.successfully_logged_out, Toast.LENGTH_SHORT).show();
updateUiForANonLoggedInUser();
}
private void updateUiForANonLoggedInUser()
{
mTemperatureValueTextView.setVisibility(View.GONE);
mTemperatureNameTextView.setVisibility(View.GONE);
mWelcomeTextView.setText(R.string.hello_relayr);
}
private void updateUiForALoggedInUser()
{
mTemperatureValueTextView.setVisibility(View.VISIBLE);
mTemperatureNameTextView.setVisibility(View.VISIBLE);
loadUserInfo();
}
private void loadUserInfo()
{
mUserInfoSubscription = RelayrSdk.getRelayrApi().getUserInfo().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<User>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Toast.makeText(ThermometerDemoActivity.this, R.string.something_went_wrong, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
#Override
public void onNext(User user) {
String hello = String.format(getString(R.string.hello), user.getName());
mWelcomeTextView.setText(hello);
loadTemperatureDevice(user);
}
});
}
private void loadTemperatureDevice(User user)
{
mTemperatureDeviceSubscription = RelayrSdk.getRelayrApi().getTransmitters(user.id).flatMap(new Func1<List<Transmitter>, Observable<List<TransmitterDevice>>>() {
#Override
public Observable<List<TransmitterDevice>> call(List<Transmitter> transmitters) {
// This is a naive implementation. Users may own many WunderBars or other
// kinds of transmitter.
if (transmitters.isEmpty())
return Observable.from(new ArrayList<List<TransmitterDevice>>());
return RelayrSdk.getRelayrApi().getTransmitterDevices(transmitters.get(0).id);
}
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<List<TransmitterDevice>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Toast.makeText(ThermometerDemoActivity.this, R.string.something_went_wrong, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
#Override
public void onNext(List<TransmitterDevice> devices) {
for (TransmitterDevice device : devices) {
if (device.model.equals(DeviceModel.TEMPERATURE_HUMIDITY.getId())) {
subscribeForTemperatureUpdates(device);
return;
}
}
}
});
}
#Override
protected void onPause()
{
super.onPause();
unSubscribeToUpdates();
}
private void unSubscribeToUpdates()
{
if (!mUserInfoSubscription.isUnsubscribed())
mUserInfoSubscription.unsubscribe();
if (!mTemperatureDeviceSubscription.isUnsubscribed())
mTemperatureDeviceSubscription.unsubscribe();
if (mDevice != null)
RelayrSdk.getWebSocketClient().unSubscribe(mDevice.id);
}
#Override
protected void onResume()
{
super.onResume();
if (RelayrSdk.isUserLoggedIn())
{
updateUiForALoggedInUser();
}
else
{
updateUiForANonLoggedInUser();
}
}
private void subscribeForTemperatureUpdates(TransmitterDevice device)
{
mDevice = device;
RelayrSdk.getWebSocketClient().subscribe(device).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<Reading>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
Toast.makeText(ThermometerDemoActivity.this, R.string.something_went_wrong, Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
#Override
public void onNext(Reading reading) {
if (reading.meaning.equals("temperature"))
mTemperatureValueTextView.setText(reading.value + "˚C");
}
});
}
}
ThermometerDemoApplication.java:
package com.vasansdomain.pavan.thermometer;
import android.app.Application;
import io.relayr.demo.thermometer.RelayrSdkInitializer;
public class ThermometerDemoApplication extends Application
{
#Override
public void onCreate()
{
super.onCreate();
RelayrSdkInitializer.initSdk(this);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="#drawable/thermometer"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:name=".ThermometerDemoApplication" >
<activity
android:name=".ThermometerDemoActivity"
android:label="#string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
activity_demo_thermometer.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context=".ThermometerDemoActivity"
android:background="#FFFFFF"
android:gravity="center">
<TextView
android:id="#+id/txt_welcome"
android:text="#string/hello_relayr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#000000"
android:textSize="20dp" />
<TextView
android:id="#+id/txt_temperature_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="60sp"
android:paddingTop="25dp"
android:paddingBottom="25dp"/>
<TextView
android:id="#+id/txt_temperature_name"
android:text="#string/title_temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:layout_gravity="center"
android:paddingBottom="25dp"/>
</LinearLayout>
strings.xml
<resources>
<string name="app_name">Thermometer</string>
<string name="action_settings">Settings</string>
<string name="hello_relayr">Hello Relayr!!!</string>
<string name="hello">Hello %s!</string>
<string name="action_log_in">Log in</string>
<string name="action_log_out">Log out</string>
<string name="successfully_logged_in">You were successfully logged in!</string>
<string name="successfully_logged_out">You were successfully logged out!</string>
<string name="unsuccessfully_logged_in">There was a problem in the log in.</string>
<string name="something_went_wrong">Oops! Something went wrong</string>
<string name="title_temperature">Temperature</string>
</resources>
This is the code for the mock mode of the app:debug->java->io->relayr->demo->thermometer
package io.relayr.demo.thermometer;
import android.content.Context;
import io.relayr.RelayrSdk;
public abstract class RelayrSdkInitializer {
public static void initSdk(Context context) {
new RelayrSdk.Builder(context).inMockMode(true).build();
}
}
This is the code for the release mode of the app:release->java->io->relayr->demo->thermometer
package io.relayr.demo.thermometer;
import android.content.Context;
import io.relayr.RelayrSdk;
public class RelayrSdkInitializer {
public static void initSdk(Context context) {
new RelayrSdk.Builder(context).inMockMode(false).build(); }
}
Is the answer possibly related to tweaking the RelayrSdkInitializer class in the debug file of the app
Build a release version of the app and try again. The debug version only provides mock data.
Because if you see another name (Hugo Domenech) than you are building the app in debug mode and that's why you see mocked data.
I am using two different Layouts for same Activity, On orientation change the activity state is not maintained. Please suggest how to do that?
You can easily store your "activity state" using onSavedInstanceState. For example:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(saveInstanceState != null) {
if(savedInstanceState.getBoolean("running") == true) {
// previously is running
} else {
// previously not running
}
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(condition) {
outState.putBoolean("running", true);
}
}
You seem to do some work in an AsyncTask and then you're changing the orientation.
Move the task you're doing from AsyncTask in an IntentService. Once the job is executed, send a broadcast with the result. In your activity, register a BroadcastReceiver in onCreate and de-register it in onDestroy.
That receiver will handle the result of your operation. You could also send from IntentService some intermediary data/progress update that can be received and processed by the same Receiver.
EDIT: to show some code.
The first search result on Google for IntentService tutorial. But I would strongly recommend reading more about Services. Also Vogella has a nice article about this.
About BroadcastReceivers.
If you want something to get you started here's a sample project I built in 15 mins:
The activity:
package com.adip.droid.sampleandroid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
public class FrontActivity extends FragmentActivity {
private TextView labelData;
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WorkerService.WORK_PROGRESS_ACTION.equals(action)) {
publishProgress(intent.getStringExtra(WorkerService.WORK_KEY_PROGRESS));
} else if (WorkerService.WORK_END_ACTION.equals(action)) {
workInProgress = false;
publishResult(intent.getStringExtra(WorkerService.WORK_KEY_RESULT));
}
}
};
protected void publishProgress(String data) {
showMessage(data);
}
protected void publishResult(String data) {
showMessage(data);
}
private void showMessage(String data) {
labelData.setText(data);
}
/**
* Maybe you need this in order to not start the service once you have an ongoing job ...
* */
private boolean workInProgress;
#Override
protected void onCreate(Bundle instanceState) {
super.onCreate(instanceState);
setContentView(R.layout.front_layout);
if (instanceState != null) {
workInProgress = instanceState.getBoolean("WORK_IN_PROGRESS", false);
}
labelData = (TextView) findViewById(R.id.labelData);
findViewById(R.id.btnWorker).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
doTheJob();
}
});
IntentFilter filter = new IntentFilter(WorkerService.WORK_END_ACTION);
filter.addAction(WorkerService.WORK_PROGRESS_ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
}
protected void doTheJob() {
if (workInProgress) {
return;
}
Intent serviceIntent = new Intent(this, WorkerService.class);
serviceIntent.setAction(WorkerService.WORK_START_ACTION);
startService(serviceIntent);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("WORK_IN_PROGRESS", workInProgress);
}
#Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
}
}
its layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="#+id/btnWorker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Start the work" />
<TextView
android:id="#+id/labelData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/btnWorker"
android:layout_centerHorizontal="true"
android:layout_marginBottom="35dp"
android:freezesText="true"
android:text="No data yet"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
and the IntentService:
package com.adip.droid.sampleandroid;
import android.app.IntentService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
public class WorkerService extends IntentService {
public static final String WORK_START_ACTION = "WORK_START_ACTION";
public static final String WORK_END_ACTION = "WORK_END_ACTION";
public static final String WORK_KEY_RESULT = "WORK_KEY_RESULT";
public static final String WORK_PROGRESS_ACTION = "WORK_PROGRESS_ACTION";
public static final String WORK_KEY_PROGRESS = "WORK_KEY_PROGRESS";
public WorkerService() {
super("WorkerService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (WORK_START_ACTION.equals(intent.getAction())) {
startProgress();
}
}
private void startProgress() {
publishProgress("Starting the job");
synchronized (this) {
try {
wait(2500);
} catch (InterruptedException ignored) {
}
}
publishProgress("Progress Point A");
synchronized (this) {
try {
wait(2500);
} catch (InterruptedException ignored) {
}
}
publishJobDone();
}
public void publishProgress(String data) {
Intent progressIntent = new Intent(WORK_PROGRESS_ACTION);
progressIntent.putExtra(WORK_KEY_PROGRESS, data);
LocalBroadcastManager.getInstance(this).sendBroadcast(progressIntent);
}
public void publishJobDone() {
Intent progressIntent = new Intent(WORK_END_ACTION);
progressIntent.putExtra(WORK_KEY_RESULT, "Job done!");
LocalBroadcastManager.getInstance(this).sendBroadcast(progressIntent);
}
}
Also don't forget to update your manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.adip.droid.sampleandroid"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".FrontActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".WorkerService"
android:exported="false" >
</service>
</application>
</manifest>
Here's my error:
*** Uncaught remote exception! (Exceptions are not yet supported across processes.)
android.util.AndroidRuntimeException: { what=1008 when=368280372 } This message is already in use.
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:171)
at android.os.Handler.sendMessageAtTime(Handler.java:457)
at android.os.Handler.sendMessageDelayed(Handler.java:430)
at android.os.Handler.sendMessage(Handler.java:367)
at android.view.ViewRoot.dispatchAppVisibility(ViewRoot.java:2748)
What I'm attempting is to have a listview, that is populated by custom list items, each list item has multiple views and each view has an onclick listener attached. When this onClickListener is pressed it sends a Message to a Handler with a what and arg1 arguments.
Clicking one of my elements fires an intent to start a new activity.
Clicking the other shows a toast.
When these are pressed in a combination I get the error above. Namely clicking the text to fire the intent, (then press back) then clicking the image to show the toast, then when you click the text to fire the intent again I get the FC.
And here is the code below, I tried to remove as much cruft as I could to get to the bones of the error:
If you want to skip to whats important look at the onClickListener's in ConversationAdapter.class and how they interact with StartPage.class
Android Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.handler.test"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".StartPage"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailsPage"
android:label="DetailsPage"
>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
StartPage.class:
package com.handler.test;
import java.util.ArrayList;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
public class StartPage extends ListActivity {
private ArrayList<Conversation> mConversations = null;
private ConversationAdapter mAdapter;
private Context mContext;
private ProgressDialog mProgressDialog;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = this;
mConversations = new ArrayList<Conversation>();
this.mAdapter = new ConversationAdapter(mContext, R.layout.inbox_row, mConversations, mHandler);
setListAdapter(this.mAdapter);
new Thread(new Runnable() {
#Override
public void run() {
getConversations();
}
}).start();
mProgressDialog = ProgressDialog.show(StartPage.this, "Please wait...", "Retrieving data ...", true);
}
private void getConversations() {
try {
mConversations = new ArrayList<Conversation>();
Conversation o1 = new Conversation();
o1.setStatus("SF services");
o1.setMessage("Pending");
mConversations.add(o1);
} catch (Exception e) {
Log.e("BACKGROUND_PROC", e.getMessage());
}
runOnUiThread(returnRes);
}
private Runnable returnRes = new Runnable() {
#Override
public void run() {
if(mConversations != null && mConversations.size() > 0){
mAdapter.notifyDataSetChanged();
for(int i=0;i<mConversations.size();i++)
mAdapter.add(mConversations.get(i));
}
mProgressDialog.dismiss();
mAdapter.notifyDataSetChanged();
}
};
private Handler mHandler = new Handler(){
#Override
public void handleMessage(Message msg) {
int convIndex = msg.arg1;
int viewTouched = msg.what;
switch(viewTouched){
case ConversationAdapter.PROF_ICON:
showNumber(convIndex);
break;
case ConversationAdapter.MESSAGE:
showMessageDetails(convIndex);
break;
}
super.handleMessage(msg);
}
};
private void showNumber(int convIndex) {
Toast.makeText(mContext, "Pressed: "+convIndex, Toast.LENGTH_LONG).show();
}
private void showMessageDetails(int convIndex) {
final Conversation conv = mConversations.get(convIndex);
Intent i = new Intent(mContext, DetailsPage.class);
i.putExtra("someExtra", conv);
startActivity(i);
}
}
DetailsPage.class
package com.handler.test;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class DetailsPage extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.i("Test", "Details Page");
}
}
Conversation.class:
package com.handler.test;
import java.io.Serializable;
public class Conversation implements Serializable {
private static final long serialVersionUID = -437261671361122258L;
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}
ConversationAdapter.class:
package com.handler.test;
import java.util.ArrayList;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class ConversationAdapter extends ArrayAdapter<Conversation> {
public static final int PROF_ICON = 0;
public static final int MESSAGE = 1;
private Context mContext;
private Handler mHandler;
private ArrayList<Conversation> mItems;
private int mXmlId;
private LinearLayout detailsOfConv;
private ImageView iconImage;
public ConversationAdapter(Context context, int textViewResourceId, ArrayList<Conversation> items, Handler handler) {
super(context, textViewResourceId, items);
this.mContext = context;
this.mItems = items;
this.mXmlId = textViewResourceId;
this.mHandler = handler;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(mXmlId, null);
}
final Message m = new Message();
m.arg1 = position;
Conversation c = mItems.get(position);
if (c != null) {
iconImage = (ImageView) v.findViewById(R.id.icon);
if (iconImage != null) {
iconImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
m.what = PROF_ICON;
mHandler.sendMessage(m);
}
});
}
detailsOfConv = (LinearLayout) v.findViewById(R.id.details);
if(detailsOfConv != null){
detailsOfConv.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
m.what = MESSAGE;
mHandler.sendMessage(m);
}
});
}
}
return v;
}
}
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip"
>
<ListView
android:id="#+id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000"
/>
</LinearLayout>
inbox_row.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="6dip">
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_marginRight="6dip"
android:src="#drawable/icon" />
<LinearLayout
android:id="#+id/details"
android:orientation="vertical"
android:layout_width="0dip"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="#+id/toptext"
android:textColor="#99FF66"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:singleLine="true"
android:text="123456789"
/>
</LinearLayout>
</LinearLayout>
My guess would be that you are sending twice the same message. Indeed in the code there is one new Message() and two mHandler.sendMessage(m) which are possibly both executed.
Try making a new message for every time you send a message.
Edited:
Message.obtain() is preferable to Message m = new Message() (because it recycles used messages under the hood)
In your case you could use new.copyFrom(old) if you need a copy of existing message.