So im quite new to Android Studio and app development in general and i had this issue for a while, with no luck of fixing it. I've figured that someone here might provide some ideas for fixing this..
Issue: The refreshing of ListView on UI thread (as suggested here) does not work for me. Here's the declaration of 'Runnable run' (in MainActivity.java):
public Runnable run;
// ...
run = new Runnable() {
public void run() {
ArrayList<String> temp1 = (ArrayList<String>) arrayList_1.clone();
View v = getLayoutInflater().inflate(R.layout.fragment_main, null);
lv_1 = (ListView) v.findViewById(R.id.listViewMe);
lv_1.setAdapter(adapter_1);
arrayList_1.clear();
arrayList_1.addAll(temp1);
adapter_1.notifyDataSetChanged();
lv_1.invalidateViews();
lv_1.refreshDrawableState();
Log.e("gig", "DONE REFRESHING");
}
};
I call the method here:
#Override
public boolean onNavigationItemSelected(MenuItem item)
{
int id = item.getItemId();
Fragment fragment = null;
if (id == R.id.nav_main) {
fragment = new FragmentMain();
} if (id == R.id.nav_history) {
fragment = new FragmentHistory();
}
if (fragment != null)
{
FragmentTransaction localFragmentTransaction = getSupportFragmentManager().beginTransaction();
localFragmentTransaction.replace(R.id.screen_area, fragment);
localFragmentTransaction.commit();
runOnUiThread(run); // here
}
((DrawerLayout) findViewById(R.id.drawer_layout)).closeDrawer(GravityCompat.START);
return true;
}
Now for the part that's confusing me: it works when i add a new item to the ListView via this method:
public void addDebtToOther(String name, String money)
{
String full = name + ", " + money + " EUR";
lv_1 = (ListView) findViewById(R.id.listViewMe);
if (lv_1!=null) {
lv_1.setAdapter(adapter_1);
arrayList_1.add(full);
adapter_1.notifyDataSetChanged();
} else {
Log.e("gig", "ListView error, lv is NULL");
}
}
That's about it, any help would be really apriciated!
In the run object, a new View is inflated, but as far as I can tell, it's not being added to the Activity's contentView or any of it's children, so it is just a View somewhere in memory that isn't being rendered onto the screen.
Did you mean to do something like this instead?:
public Runnable run;
// ...
run = new Runnable() {
public void run() {
ArrayList<String> temp1 = (ArrayList<String>) arrayList_1.clone();
// View v = getLayoutInflater().inflate(R.layout.fragment_main, null);
// lv_1 = (ListView) v.findViewById(R.id.listViewMe);
lv_1 = (ListView) findViewById(R.id.listViewMe);
lv_1.setAdapter(adapter_1);
arrayList_1.clear();
arrayList_1.addAll(temp1);
adapter_1.notifyDataSetChanged();
lv_1.invalidateViews();
lv_1.refreshDrawableState();
Log.e("gig", "DONE REFRESHING");
}
};
Ok so after about a week of head-banging, with the help of #Eric i've figured it out. Turns out i needed to call the run() method in my fragment's .onStart(), like this:
#Override
public void onStart() {
super.onStart();
((MainActivity)getContext()).run.run();
}
Thanks again Eric!
Related
I have a tabhost with several tabs and each tab contain a certain number of operations which are listed in a listview. To populate that listview I use an ArrayList.
First time tabs are created evertything works fine. The issue comes when I try to filter the list by year. The process of filtering works fine as I can see the filtered list in debug and it's fine.
The issue is that after filtering, i recreate the tabs in order to fill all listviews again. To open tabs I use this code. It creates as many tabs as different currencies there are in the list:
public static void openFragments(FragmentTabHost tabHost, ArrayList<Posicion> positions, Class FragmentResumen, Class FragmentDetails ) {
//==========================================================================================
// This method open as many tabs as different currencies there are in positions list
//==========================================================================================
ArrayList<String> currencies = Currency.getDifferentCurrencies(positions);
tabHost.clearAllTabs();
for (int i = 0; i < currencies.size() + 1; i++) {
String tabName = "", tabSpec = "";
Class fragmentToOpen;
Bundle arg1 = new Bundle();
//A general tab is first created
if (i == 0)
{
tabName = "All";
tabSpec = "General";
arg1.putString("moneda", tabName);
arg1.putSerializable("posiciones", positions);
fragmentToOpen = FragmentResumen;
}
//The rest of tabs for currencies are created
else
{
tabName = currencies.get(i - 1);
tabSpec = "Tab" + (i - 1);
arg1.putString("moneda", tabName);
arg1.putSerializable("posiciones", positions);
fragmentToOpen = FragmentDetails;
}
tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(tabName), fragmentToOpen, arg1);
}
}
As I told before, this works fine always.
First time I need to create tabs I call it by using:
openFragments(tabHost, positions, FragmentResumenMedio.class, FragmentDetailsMedio.class);
Then I have a button that shows a DatePicker and when user selects a year I close the dialog and redraw tabs as follows:
ArrayList<Posicion> positionsFiltered = General.makeHardCopyOfArrayListPosition(positions);
for(Posicion posicion : positionsFiltered)
{
Boolean matchFilters = filterPositionsByYear(posicion, year + "");
if(matchFilters == false){
positions.remove(posicion);
}
}
General.openFragments(tabHost, positions, FragmentResumenMedio.class, FragmentDetailsMedio.class);
When I debug this last function I can see that positions have the correct value after filtering but when I click the new tab, it shows the list without filtering and I don't know how could I solve this issue.
Thanks a lot.
EDIT
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//Initialize view and tabhost
View rootView = inflater.inflate(R.layout.fragment_medio, container, false);
tabHost = (FragmentTabHost) rootView.findViewById(android.R.id.tabhost);
tabHost.setup(getActivity(), getChildFragmentManager(), android.R.id.tabcontent);
return tabHost;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//onCreatedView is only called the first time so we must ensure that tabhost is not null before adding tabs
if(tabHost == null) {
tabHost = (FragmentTabHost) getView().findViewById(android.R.id.tabhost);
tabHost.setup(getActivity(), getChildFragmentManager(), android.R.id.tabcontent);
}
FloatingActionButton floatingActionButton = (FloatingActionButton) getView().findViewById(R.id.floatingButton);
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
positions = new ArrayList<>(positionsFiltered);
createDialogWithoutDateField().show();
}
});
//Check if any update has been made since the last open
SharedPreferences prefs = getActivity().getPreferences(MODE_PRIVATE);
Boolean updateMedioRequired = prefs.getBoolean(updateOperationsMedioPlazo, true);
if (updateMedioRequired != null)
{
if (updateMedioRequired == true)
{
//Update variable that indicates if changes have been made or not
SharedPreferences.Editor editor = getActivity().getPreferences(MODE_PRIVATE).edit();
editor.putBoolean(updateOperationsMedioPlazo, false);
editor.apply();
//Check if there are previously stored operations
if (operations.size() > 0)
{
//Show a progressDialog as prices have to be downloaded from internet and this can be a time consumming task
progress = ProgressDialog.show(getActivity(), "Obteniendo precios",
"Un momento por favor...", true);
//Generate positions from operations list and wait for result in "onStockPriceResult". If there are no changes, positions variable has already values
if(positions.size() == 0) {
new Thread(new Runnable() {
#Override
public void run() {
positions = MedioPlazoCalculations.generatePositions(listener, getActivity(), operations);
}
}).start();
}
}
else
{
Toast.makeText(getActivity(), "Aún no se ha introducido ninguna operación", Toast.LENGTH_LONG).show();
}
}
else
{
//If no update needed, variable coming from MainActivity has positionList. Open as many new fragments as currencies there are in positionsList
General.openFragments(tabHost, positions, FragmentResumenMedio.class, FragmentDetailsMedio.class);
}
}
}
EDIT 2:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
{
if(getActivity()!=null)
{
Bundle bundle = this.getArguments();
positions = (ArrayList<Posicion>) bundle.getSerializable("posiciones");
moneda = (String) bundle.getString("moneda");
}
}
}
Edit 3: If I place the commented instruction, filtering does not work. If I remove it, filtering works but I cant filter again because the value of the list has the filtered version not the original one
floatingActionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
positions = new ArrayList<>(positionsFiltered);
createDialogWithoutDateField().show();
}
});
I'm working on an SDK for making tutorials. In this SDK I present a spotlight fragment in which I basically create a background canvas there I draw a darker background and a transparent rectangle that focuses on the desired view.
In some cases, this view might move. For example, the developer that uses my SDK creates a timed collapse of a view before the focused view which makes the focused view to move and as a result, my spotlight stays in the wrong location.
The question is: How can I recognize a view movement on the screen so I can update my spotlight fragment accordingly?
The only solution I came up by now is the following 'active' solution, I'm running a Task every half a second that checks the LocationOnScreen of the target view. and if the target view changes it's coordinates I update the fragment. This solution works but I'm still looking for a 'passive' solution that updates me on the location changes instead of testing it every half a second:
#Override
public void onStart() {
super.onStart();
final View targetView = mDrawDataPojo.getWalkthroughMetaPojo().getTargetView().getView();
if (targetView != null) {
targetView.getLocationOnScreen(mOriginalLocationOnScreen);
mTimer = new Timer();
mTimer.schedule(new TargetViewChangeListener(), 0, 500);
}
...
}
#Override
public void onPause() {
super.onPause();
if (mTimer != null) {
mTimer.cancel();
}
...
}
class TargetViewChangeListener extends TimerTask {
public void run() {
int[] currentLocation = new int[2];
mDrawDataPojo.getWalkthroughMetaPojo().getTargetView().getView().getLocationOnScreen(currentLocation);
if (currentLocation[0] != mOriginalLocationOnScreen[0] || currentLocation[1] != mOriginalLocationOnScreen[1]) {
final boolean isActionBar = ABUtils.isActionBarActivity(getActivity());
final int containerId;
try {
mDrawDataPojo.getWalkthroughMetaPojo().setTargetView(new SpotlightTargetView(getActivity(), mDrawDataPojo.getWalkthroughMetaPojo().getTargetView().getView()));
containerId = AndroidUtils.getContainerId(getActivity(), isActionBar);
ABPromotionFragment abPromotionFragment = ABPromotionFragment.newInstance(mDrawDataPojo.getViewDataPojo(), null, mDrawDataPojo.getWalkthroughMetaPojo());
FragmentManager fragmentManager = getActivity().getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
fragmentTransaction.replace(containerId, abPromotionFragment);
fragmentTransaction.commitAllowingStateLoss();
} catch (Exception e) {
ABLogger.d("TargetViewChangeListener - TimerTask - exception: " + e);
}
}
}
}
Found a much better solution using the OnPreDrawListener:
private final ViewTreeObserver.OnPreDrawListener mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if (!mAttached) {
removePreDrawObserver(null);
return true;
}
handleViewDraw();
return true;
}
};
The onPreDraw method will be called each time the view is going to be redrawn.
Where handleViewDraw method will look like the follows:
private void handleViewDraw() {
if (if mViewAnchor != null) {
View view = mViewAnchor.get();
if (null != view && view.getVisibility() == VISIBLE) {
view.getLocationOnScreen(mTempLocation);
if (mOldLocation == null) {
mOldLocation = new int[]{mTempLocation[0], mTempLocation[1]};
}
if (isTargetViewLocationChanged()) {
handleVisibleTargetViewLocationChange();
}
mOldLocation[0] = mTempLocation[0];
mOldLocation[1] = mTempLocation[1];
} else {
mView.setVisibility(INVISIBLE);
}
} else {
mView.setVisibility(INVISIBLE);
}
}
private boolean isTargetViewLocationChanged() {
Log.d(TAG, "Old: " + mOldLocation[1] + " ,TEMP: " + mTempLocation[1]);
return mOldLocation[0] != mTempLocation[0] || mOldLocation[1] != mTempLocation[1];
}
Using this method you will be notified only when the view moved, in difference with the 'active' solution that is supplied in the other answer this is a 'passive' solution which will run the handleVisibleTargetViewLocationChange method only when the view has actually moved.
Hello guys I am for first time here so if there are some mistakes with my problem just notify me to correct my self. My problem is in the adapter I think. My application is with 3 fragment tabs. In each tab I have a ListView with some items. Also I have button in each tab that updates,deletes or adds items. I make that by popping up an AleartDialog for adding a new item or clicking on the item from the list to update it or delete it. So after I login in my application I can do what ever I want with that list. Its not a problem. I can rotate the screen and my list still updates. Changing the tabs its not a problem also. The problem comes when I go to other fragment I click on the button for adding or on some of the items for updating and the AleartDialog pops up. After that when I close it I return to the first tab. Trying to add an item doesn't update the list and I see the same items. Deleting the item from the list then throws exception IndexOutOfBounds because the item its not in the list. I think I use arrayAdapter.notifyDataSetChanged() properly. Here I will share some other code.
Like that I update my list. In the jsonObject is list the with the items.
private void fillList(Gson gson, String jsonObject) {
listWithNames.clear();
Type collectionType = new TypeToken<ArrayList<PersonalSaveDTO>>() {
}.getType();
personalSaveList = gson.fromJson(jsonObject, collectionType);
Log.wtf(TAG, "OT REQUEST: " + personalSaveList);
for (int i = 0; i < personalSaveList.size(); i++) {
listWithNames.add(personalSaveList.get(i).getName());
}
Log.wtf(TAG, "fillList: " + listWithNames);
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
arrayAdapter.notifyDataSetChanged();
buttonEnable(true);
}
});
}
Like that I starts the fragment.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View viewFragment = inflater.inflate(R.layout.user_fragment, container, false);
listViewUser = (ListView) viewFragment.findViewById(R.id.listViewForUser);
addItem = (Button) viewFragment.findViewById(R.id.buttonAddUserElements);
updateList = (Button) viewFragment.findViewById(R.id.refreshListUser);
updateList.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
makeRequestForList(viewFragment);
}
});
addItem.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
addElement(viewFragment);
}
});
configureArrayAdapter(viewFragment);
setListenerForUserList(viewFragment);
if (savedInstanceState != null) {
isRequestMade = savedInstanceState.getBoolean("isRequestMade");
listWithNames = savedInstanceState.getStringArrayList("listWithNames");
personalSaveList = savedInstanceState.getParcelableArrayList("personalSaveList");
arrayAdapter.addAll(listWithNames);
arrayAdapter.notifyDataSetChanged();
}
if (!isRequestMade) {
makeRequestForList(viewFragment);
isRequestMade = true;
}
return viewFragment;
}
And here is how I create the adapter.
private void configureArrayAdapter(View view) {
listWithNames = new ArrayList<>();
arrayAdapter = new ArrayAdapter<String>(view.getContext(),
R.layout.list_personal_group_fragment, R.id.adapterFragmentPersonalGroups, listWithNames);
arrayAdapter.clear();
listViewUser.setAdapter(arrayAdapter);
}
I have put Caldroid inside my activity with this code in onCreate method.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
app_font = Typeface.createFromAsset(getAssets(), "custom_font.ttf");
dbHelper = new DatabaseHelper(this);
caldroidFragment = new CaldroidFragment();
if (savedInstanceState != null) {
caldroidFragment.restoreStatesFromKey(savedInstanceState,
"CALDROID_SAVED_STATE");
} else {
Bundle args = new Bundle();
args.putInt(CaldroidFragment.MONTH, calendar.get(Calendar.MONTH) + 1);
args.putInt(CaldroidFragment.YEAR, calendar.get(Calendar.YEAR));
args.putBoolean(CaldroidFragment.ENABLE_SWIPE, true);
args.putBoolean(CaldroidFragment.SIX_WEEKS_IN_CALENDAR, true);
args.putInt(CaldroidFragment.START_DAY_OF_WEEK, CaldroidFragment.MONDAY);
caldroidFragment.setArguments(args);
}
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.replace(R.id.calendar_holder, caldroidFragment, FRAGMENT_TAG);
t.commit();
fragmentMounted = true;
CaldroidListener listener = new CaldroidListener() {
#Override
public void onSelectDate(Date date, View view) {
Intent k = new Intent(MainActivity.this, DateActivity.class);
k.putExtra(TIME, date.getTime());
MainActivity.this.startActivity(k);
}
#Override
public void onChangeMonth(int month, int year) {
if (marker != null) {
marker.stopWorking();
}
marker = new DateMarker(MainActivity.this, month, year);
marker.start();
}
#Override
public void onLongClickDate(Date date, View view) {
// do nothing
}
#Override
public void onCaldroidViewCreated() {
// do nothing
}
};
caldroidFragment.setCaldroidListener(listener);
}
As you notice, there is a Thread being called inside listener every time user switches to other month. This thread accesses database and marks dates which have events.
NOTE: this is just inner sample of code inside run method of that thread.
// more code above
if (MainActivity.dbHelper.getNotesOnDate(date).size() == 0) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
activity.caldroidFragment.setBackgroundResourceForDate(R.color.SeaGreen, date);
activity.caldroidFragment.setTextColorForDate(R.color.white, date);
activity.caldroidFragment.refreshView();
}
});
}
//more code under
I start another activity for user to log in while all this loads. The problem is only markings that are made BEFORE replacing fragment with holder view in onCreate method. How can I refresh caldroidFragment after replacing it with its holder? refreshView() seems not to be working after placing it.
EDIT: I did research and I notice Caldroid is using app V4 Fragment. I tried to detach and reattach it to get refresh effect but I only got NullPointerException while trying to do it.
I found the answer, do not use Java's Date class, it does not work right.
Use Joda's DateTime. Caldroid works fine with it. Also, I found minor bug in my database access, but it should have worked even though it was there.
I am using a ListFragment for displaying a list from a database in my activity. I have included a search function. Unfortunately the "old" ListFragments seem to remain in the background and the ListFragments containing the result of the query are displayed on top of it. How can I avoid, that the old ListFragments are displayed?
My FragmentActivity:
private Button buttonSearch;
private TextView searchString;
public static String search = null;
static IShoppinglist shoppinglistManager;
static IAktionen aktionenManager;
private AktionenListListFragment listFragment;
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "ListFragmentActivity created");
super.onCreate(savedInstanceState);
setContentView(R.layout.articlelist);
shoppinglistManager = new Shoppinglist(this);
aktionenManager = new Aktionen(this);
buttonSearch = (Button) findViewById(R.id.search_Button);
buttonSearch.setOnClickListener(searchListAktionen);
//show all entries on start
listFragment = new AktionenListListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_articlelist, listFragment).commit();
}
OnClickListener searchListAktionen = new OnClickListener() {
public void onClick(View v) {
try{
searchString = (TextView) findViewById(R.id.input_search_bezeichnung);
search = searchString.getText().toString().trim();
Log.d(TAG, "search Button clicked "+search);
if(search.trim().length()==0){
search=null;
}
//show all entries on start
listFragment = new AktionenListListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_articlelist, listFragment).commit();
}catch(Exception ex){
ex.printStackTrace();
}
}
};
Thanks in advance,
update:
thank you for your answers. I tried to implement them, but the main problem seems to be nowthat the onCreate and onActivityCreated method in the ListFragment are called twice (as I can see in my log messages).
my new code:
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "ListFragmentActivity created");
super.onCreate(savedInstanceState);
//force commit
getSupportFragmentManager().executePendingTransactions();
if(getSupportFragmentManager().findFragmentByTag(tag) == null) {
setContentView(R.layout.articlelist);
shoppinglistManager = new Shoppinglist(this);
aktionenManager = new Aktionen(this);
buttonSearch = (Button) findViewById(R.id.search_Button);
buttonSearch.setOnClickListener(searchListAktionen);
listFragment = new AktionenListListFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_articlelist, listFragment,tag).commit();
}else{
Log.d(TAG, "ListFragment already exists");
}
}
I tried to set a unique tag for my ListFragment but this solution does not work.
I guess that one of the ListFragments is displayed in the background and the other is updated.
So first you need to stop making new ListFragments everytime your list is refreshed and just have a public method in your ListFragment that the Activity can call to restart the loader with the proper parameters. Then:
In your onLoadFinished(),
you should make a new adapter with the list you want to replace it with
myAdapter = new AktionenListCustomCursorAdapter(getActivity(), myCursor);
and call:
this.getListView().setAdapter(myAdapter);
So:
public void onLoadFinished(Loader<Cursor> mAdapter, Cursor myCursor) {
if(myCursor!=null){
//getting the data from the database
Log.d(TAG, "search String "+AktionenListFragmentActivity.search);
if(AktionenListFragmentActivity.search==null){
myCursor = AktionenListFragmentActivity.aktionenManager.fetchAllArticles();
}else{
myCursor = AktionenListFragmentActivity.aktionenManager.fetchItemsByBezeichnung(AktionenListFragmentActivity.search);
}
myAdapter = new AktionenListCustomCursorAdapter(getActivity(), myCursor);
this.getListView().setAdapter(myAdapter);
}
}
Hopefully this solved your question as I understood it. If you have any questions please leave it in the comment below and I will expand my answer. If it worked for you please accept answer. :D