I have an app I am developing that polls a vehicle's sensors via the obd2 port. Everything is going great and now I am adding a new activity that shows which sensors are supported and which aren't.
The issue now is that there is a possibility that a user might start the activity before all the supported commands have been checked, thus the ListView needs to be updated when the variable does change.
Now all the available pids are stored on a variable on the mainview, but once it changes it gets broadcasted and updated on the supportview.
The issue I am having is that it doesn't want to update the listview automatically. I have gone through multiple threads on SF and haven't found a solution thus far. I have tried everything from creating a custom handler and receiver to running it on the UI-thread.
This is the complete activity:
public class SupportedView extends ListActivity {
ArrayAdapter<Spanned> mAdapter;
ArrayList<Spanned> commandsList;
private String availpids;
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"ObdReader");
if (!wakeLock.isHeld()){
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
wakeLock.acquire();
}
String pids32 = intent.getParcelableExtra("pids");
updateList(pids32);
Log.d("receiver", "Got message: Updated PIDS" );
mAdapter.notifyDataSetChanged();
Toast.makeText(context, "Received and tried update", Toast.LENGTH_LONG).show();
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sl_listview);
//Set fullscreen
availpids = MainView.pids32_val;
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("pids"));
updateList(availpids);
mAdapter = new ArrayAdapter<Spanned>(this, R.layout.sllist_item, commandsList);
setListAdapter(mAdapter);
//ListView lv = getListView();
}
public void updateList(String listPids){
commandsList = new ArrayList<>();
final ArrayList<Spanned> tmpEnabled = new ArrayList<>();
final ArrayList<Spanned> tmpDisabled = new ArrayList<>();
for (ObdCommand Command : ObdConfig.getCommands()) {
int commandKey = Command.getKey();
if(commandKey > 0 && commandKey < 999 && listPids != null) {
boolean isSupported = String.valueOf(listPids.charAt(commandKey - 1)).equals("1");
if (isSupported) {
tmpEnabled.add(Html.fromHtml( "<font color=\"green\">" + Command.getName() + " isn't supported</font>"));
}else {
tmpDisabled.add(Html.fromHtml("<font color=\"red\">" + Command.getName() + " is supported</font>"));
}
}
}
commandsList.addAll(tmpEnabled);
commandsList.addAll(tmpDisabled);
}
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
super.onDestroy();
}
}
Let's take a look at your 'updateList()' method:
it takes the String with the pids and creates and populates a
new ArrayList()
named 'commandsList'.
In fact, you are using the same name for two different objects. The other object is the ArrayList<Spanned> you declared as a class variable (member) and assigned to be the data source for the ListView in your 'onCreate()' method.
I think what you meant to do in your method 'updateList()' was to empty the data source list and then fill it with the pids.
To achieve that, change the method as follows:
public void updateList(String listPids)
{
// empty the data source list:
commandsList.clear();
final ArrayList<Spanned> tmpEnabled = new ArrayList<>();
final ArrayList<Spanned> tmpDisabled = new ArrayList<>();
for (ObdCommand Command : ObdConfig.getCommands())
{
// keep this part as before ...
}
commandsList.addAll(tmpEnabled);
commandsList.addAll(tmpDisabled);
// now, you have indeed changed the data set :)
}
In addition to that, in order to make sure that the BroadcastReceiver is available when needed, you should register it in 'onResume()' and unregister it in 'onPause()' (the method 'onDestroy()' is not guaranteed to be called).
Related
I have an audio app with the home Activity containing a list of items. The user selects an item and I pass an ID to another Activity which has the controls (play/pause/volume, etc). The audio playback is handed in a MediaBrowserService. I need to detect if the item the user selects is currently playing but I can't figure out how outside of saving the ID in local storage (SharedPrefs or SQlite).
I pass the ID of the item from the second Activity to the MediaBrowserService though a Bundle. I thought I could then retrieve the ID in the second Activity using getExtras() but it always returns 0 or null, depending on which code I use (see below).
I'm not opposed to using local storage but seem like there should be a better way. This is what I have so far:
public class EpisodeActivity extends Activity {
private MediaBrowserCompat mMediaBrowserCompat;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Bundle extras = new Bundle();
extras.putInt("episodeid", getIntent().getExtras().getInt("episodeid")); //passed in from Home Activity
mMediaBrowserCompat = new MediaBrowserCompat(
this,
new ComponentName(this, MediaPlayerService.class),
mMediaBrowserCompatConnectionCallback,
extras
);
mPlayButton.setOnClickListener(view -> {
final Bundle extras = new Bundle();
extras.putInt("episodeid", getIntent().getExtras().getInt("episodeid")); //passed in from Home Activity
String url = "http://www.example.com/media.mp3"
MediaControllerCompat.getMediaController(mActivity).getTransportControls().playFromUri(Uri.parse(uri), extras);
});
if (MediaControllerCompat.getMediaController(mActivity).getPlaybackState() != null &&
MediaControllerCompat.getMediaController(mActivity).getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING) {
int episodeID = mMediaBrowserCompat.getExtras().getInt("episodeid"); //always returns 0
//also tried this but getExtras is null
int episodeID = MediaControllerCompat.getMediaController(mActivity).getExtras().getInt("episodeid");
}
}
}
public class MediaPlayerService extends MediaBrowserServiceCompat {
private MediaSessionCompat mMediaSessionCompat;
#Override
public void onCreate() {
super.onCreate();
final ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
mMediaSessionCompat = new MediaSessionCompat(getApplicationContext(), getString(R.string.app_name), mediaButtonReceiver, null);
mMediaSessionCompat.setCallback(mMediaSessionCallback);
...
}
private MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() {
#Override
public void onPlayFromUri(final Uri uri, final Bundle extras) {
super.onPlayFromUri(uri, extras);
int episodeId = extras.getInt("episodeid");
String url = GetUrl(episodeId);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(uri);
mMediaPlayer.prepareAsync();
...
}
}
}
media_controller.getMetadata().getDescription().getMediaId() may be what you're looking for. If not, maybe try using MediaMetadataCompat.Builder() to set some metadata to each of your episodes
MediaMetadataCompat.Builder().putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, episodeID).build()
something like that, I don't think you need to use the METADATA_KEYs either, you can probably replace that with "episode id" or whatever string you want to use as a key.
edit: if you go the metadata route, you may want to use that to build mediaitems.
MediaMetadataCompat episode_meta = new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, episodeID)
.build();
episode_mediaitem = new MediaBrowserCompat.MediaItem(episode_meta.getDescription(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE));
Hope i helped
edit: using the media items enables you to use the media_controller.getMetadata().getDescription().getMediaId() from above, I think
mPlayButton.setOnClickListener(view -> {
final Bundle extras = new Bundle();
extras.putInt("episodeid", getIntent().getExtras().getInt("episodeid")); //passed in from Home Activity
String url = "http://www.example.com/media.mp3"
MediaControllerCompat.getMediaController(mActivity).getTransportControls().playFromUri(Uri.parse(uri), extras);
});
here you should put url instead uri when you parse Uri.
I´m pretty new in android. I have made communication between two Apps with BroadcastReceiver and intentServices .
The thing is, I want to send information to the app2 from app1. In app1 I need to access a variable which is in MainActivity.class , I need to send it to servicev.class (the service where the intent is handled) but the variable "res" is null when I access it, why does that happen? (App2 calls app1 onHandleIntent and it breaks in res.getOtp() ) I try to create an extra setter getter class and also an intent but getIntent() does not work inside onHandleIntent... how can I achieve to pass res.getOTP (string) ? I really dont want to use SQLite
servicev:
public class servicev extends IntentService {
private static final int RESULT_OK = 1;
protected ResultReceiver mReceiver;
public servicev() {
super("yeah");
}
#Override
protected void onHandleIntent(Intent intent) {
//I receive here the intent from app2 and I need to response with res.getOTP()
helper h = new helper();
String val = intent.getStringExtra("foo");
Intent in = new Intent("com.banorte.bem.movil.veriToken.SendBroadcast");
in.putExtra("resultCode", this.RESULT_OK);
in.putExtra("resultValue", "My Result Value. Passed in: " + h.getRes().getOtp()); //h here is null... setter and getter approach does not work... maybe sqlite could work but it is necesary?
sendBroadcast(in);
}
}
MainActivity:
public class MainActivity extends AppCompatActivity {
VTTokenAPI api;
TextView textView;
Button button;
EditText input;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidSetup.getInstance().init(this);
helper h = new helper();
api = new VTTokenAPI("FFFFFF");
res = api.getStatus();
res.getOtp(); //correct value
h.setRes(res);
setContentView(R.layout.activity_main);
}
}
helper:
public class helper {
public VTResult getRes() {
return res;
}
public void setRes(VTResult res) {
this.res = res;
}
VTResult res;
}
You are trying to instantiate a new MainActivity which is not the same as the running activity but a new instance.
If you need your IntentService to be able to get data from a running Activity you have options such as using SharedPreferences or SQLite. Instead of keeping the data in memory try to persist it in some database in the onCreate and then try to read it from the storage during handleIntent
How can I update ListView in the other Fragment?
public class ChooseCS extends FragmentActivity {
final private Context context = this;
private HashMap<String, List<String>> mCitiesStreets = null;
private View rootViewStreetChangeFragment = null;
private SimpleAdapter adapter;
...
private static final int NUM_PAGES = 3;
private ViewPager mPager;
private PagerAdapter mPagerAdapter;
...
and two Fragments
public class CityChangeFragment extends Fragment {
and
public class StreetChangeFragment extends Fragment {
...
mMapDataAdapter.put("streets", fillcities);
adapter = new SimpleAdapter(
rootViewStreetChangeFragment.getContext(),
mMapDataAdapter.get("streets"), R.layout.grid_streets_4_7,
from, to);
mDataListViewStreets.setAdapter(adapter);
...
I need make update ListView in StreetChangeFragment from CityChangeFragment where I doing changing data
You can use LocalBroadcastManager to achieve this.
In your StreetChangeFragment write below code
#Override
public void onCreate(Bundle savedInstanceState) {
...
// Register to receive messages.
// We are registering an observer (mMessageReceiver) to receive Intents
// with actions named "custom-event-name".
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}
// Our handler for received Intents. This will be called whenever an Intent
// with an action named "custom-event-name" is broadcasted.
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
#Override
protected void onDestroy() {
// Unregister since the activity is about to be closed.
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}
And use below method to send broadcast message from CityChangeFragment to StreetChangeFragment
private void sendMessage() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my message!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Note: You can pass data using intent (here you can position also on which you want to update data in listview)
You can check below link to learn more about LocalBroadcastManager
https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
Interface is good idea,The idea is basically to define an interface and let the activity implement that interface.
Once it has implemented that interface, you could do anything you want in the method it overrides.
There is a good tutorial on Simple Developer Blog how to do exactly this kind of thing.
And you need t declare a method in fragment and it can be executed from the activity by getting the fragment instance from adapter like
Fragment fragment= mPagerAdapter.getItem(int positon);
((StreetChangeFragment )).updateList();
in StreetChangeFragment declare method and do what else you want, Thank you
public class MainActivity {
private MyListViewHelper mTimelineHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
mListViewHelper = new MyListViewHelper();
mListViewHelper.createListView();
startService(new Intent(this, MyService.class));
}
}
MyService class:
public class MyService extends Service {
private int mCount = 0;
public static final long NOTIFY_INTERVAL = 10 * 1000;
private static MyListViewHelper mListViewHelper;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
// cancel if already existed
if (mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
#Override
public void onDestroy() {
mTimer.cancel();
}
private class TimeDisplayTimerTask extends TimerTask implements LocationListener {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
//DO SOMETHING HERE
...........
mCount++;
if(mCount==4){
mListViewHelper = new MyListViewHelper()
mListViewHelper.addItemToList("ABCD");
}
}
});
public boolean isMainActivityRunning(string packageName) {
ActivityManager activityManager = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(Integer.MAX_VALUE);
for (int i = 0; i < tasksInfo.size(); i++) {
if(tasksInfo.get(i).baseActivity.getPackageName.toString().equals(packageName)
return true;
}
return false;
}
}
}
MyListViewHelper class:
public class MyListViewHelper {
private ListView mListView;
private ArrayList<String> mArrayList;
public MyListViewHelper () {
}
public void createListView(){
mArrayList = new ArrayList<String>();
mListView = (ListView) activity.findViewById(R.id.listView1);
// I make a short version,so we suppose adapter is already prepared here
mListView.setadapter(adapter);
}
public void addItemToList(String myString){
mArrayList.add(myString);
adapter.notifiDateSetChanged();
}
}
What I want is for every 10 seconds, service will do something, and then if it do that 4 times, I will add one item to ListView. I forgot to save logcat before change it back to the time before I edited, so there is no logcat, sorry for that. But I'm pretty sure there is nothing wrong with others and the problem is just somewhere in the code above since I'm leanring Android and I don't have much knowledge about Service. Please teach me to fix that!
You could use EventBus to tell your Activity that the Service is updating the list.
For example...
Your Activity:
public void onEventMainThread(ListUpdateEvent event) {
mList.add(event.getValue());
mAdapter.notifyDataSetChanged();
}
Your Service:
EventBus.getDefault().post(new ListUpdateEvent("Value to add"));
ListUpdateEvent is a simple POJO that lets you share data. Using onEventMainThread allows you to automatically protect the list from being updated on a background thread.
Here is info about how to send messages from services to activity components. So you just need to send data about new list item.
Have you tried changing adapter.notifiDateSetChanged(); to adapter.notifyDataSetChanged();
And by the way, the best way to update your activity via service is to register a broadcast receiver in your activity's onResume() and send a broadcast from your service when you want to update the activity and put your data as an extra. Don't forget to unregister() your receiver in onPause() and to declare your service in the Manifest.
You can read more about data flow from service via broadcast to activity here http://www.truiton.com/2014/09/android-service-broadcastreceiver-example/
You can't. It is not possible to update UI components in an Activity from a Service. Or, if you'd find a way to do that, I would say it is a bad idea*.
I think the EventBus implementation of Knosses is a good idea. I would like to suggest an implementation using a database, Loaders and Broadcasts.
The Activity with the ListView would use a CursorLoader to get the list content from the data source. The Service would add the items to the database each NOTIFY_INTERVAL and send out a Broadcast. The Activity would register a BroadcastReceiver that listens to the Broadcasts send by the Service, on receiving a Broadcast in the Activity, call getLoaderManager().restartLoader(), so the content in the ListView will refresh.
Edit:
* Let me elaborate a bit on that; as you can also bind a Service to your Activity. In that case, it is perfectly valid to update a UI component through the binding.
I have no idea why my Android application is crashing. I noticed it happened when I put in the simple firstTime variable, but I'm honestly clueless to why the firstTime variable would make the whole application crash. Every other part works if I take out the if statement. This is what I have put in so far:
public class MainActivity extends Activity {
static int firstTime = 0;
BroadcastReceiver receiver = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(firstTime == 0){
logTime(false);
firstTime++;
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
receiver = new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent arg1) {
if(arg1.getAction().equals(Intent.ACTION_SCREEN_OFF)){
logTime(true);
}
else if(arg1.getAction().equals(Intent.ACTION_SCREEN_ON)){
logTime(false);
}
}
};
registerReceiver(receiver, filter);
}
Might anyone know why the firstTime variable crashes my application? thanks!
I didn't think of this before, I was thinking you wanted to just increment the int, so I wasn't thinking about calling logTime() again, and didn't see how it could crash with such a simple concept.
Your issue is most likely the order, since with your current code the textView in the method will be null and will throw an NPE when accessed.
This is because the findViewById() in logTime() will return null (a content view isn't set - so there isn't anything to find):
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(firstTime == 0){
logTime(false);
firstTime++;
}