I have a strange issue & I'm hoping somebody can shed some light on this:
I'm building a dead basic Android app that needs to build a WebRTC video conference & maintain the socket/conference/video on orientation changes. I created a singleton to maintain the connection & all works fine except... after an orientation change my booleans are read incorrectly by the main activity.
The flow is basically:
Fragment state booleans saved to singleton in onDestroyView()
Fragment state booleans loaded from singleton in onCreateView()
The values are correct within the fragment but when I try to read them from the main activity, they are always "false". Why is this?
Also, when I pass them as parameters, they are correct. Why is this?
(For both questions, see communicatorReady() in main activity)
Main activity:
public class MainActivity extends Activity implements Communicator.OnEventListener{
private Communicator communicator = null;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main); // Point to layout/main.xml
// Add fragment to app
communicator = new Communicator();
if(savedInstanceState == null){
getFragmentManager()
.beginTransaction()
.add(R.id.alContainer, communicator)
.commit();
}
}
// Event listener
public void communicatorReady(boolean communicatorReady, boolean communicatorConnected, boolean userConnected){
// Also tried with a communicator.getCommunicatorReady() func, same incorrect result
Log.i(TAG, "communicator.communicatorReady " + (communicatorReady ? "true" : "false")); // Read from object, always false
Log.i(TAG, "communicatorConnected " + (communicatorConnected ? "true" : "false")); // Passed as parameter, correct value
}
}
Fragment:
public class Communicator extends Fragment{
// Communicator stuff
private WebView webComm = null;
private ServiceInfoEvent servInfo = null;
public boolean communicatorReady = false;
public boolean communicatorConnected = false;
public boolean userConnected = false;
// WebRTC stuff
private LocalMedia localMedia = null;
private Conference conference = null;
public CommunicatorEngine communicatorEngine = null;
// Event messages
OnEventListener eventListener;
public interface OnEventListener{
public void communicatorReady(boolean communicatorReady, boolean communicatorConnected, boolean userConnected);
}
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
// Check event listener interface is implemented
try{
eventListener = (OnEventListener) activity;
}
catch(ClassCastException ex){
throw new ClassCastException(activity.toString() + " must implement OnEventListener");
}
}
// Setup the view & comms on fragment creation
public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState){
View rootView = inflater.inflate(R.layout.alview, parentViewGroup, false);
communicatorEngine = CommunicatorEngine.getInstance(getActivity());
webComm = communicatorEngine.getWebComm();
if(!communicatorEngine.isWebCommInitialised()){
// Comm webview not initialised, do the initial setup
RelativeLayout.LayoutParams flp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
webComm.setLayoutParams(flp);
webComm.getSettings().setJavaScriptEnabled(true);
webComm.addJavascriptInterface(new ALCTranslator(getActivity()), "appJSInterface");
webComm.loadUrl("file:///android_asset/communicator.html");
webComm.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
webComm.setBackgroundColor(Color.TRANSPARENT);
communicatorEngine.setWebCommInitialised(true);
}
else{
// Webview stuff has already run, restore previously saved items
conference = communicatorEngine.getConference();
String peerID = communicatorEngine.getConferencePeerId();
communicatorReady = communicatorEngine.isCommunicatorReady();
communicatorConnected = communicatorEngine.isCommunicatorConnected();
userConnected = communicatorEngine.isUserConnected();
// Add remote video to our view if there is an active conference
if(conference != null && peerID != null){
try{
RelativeLayout incomingStreamContainer = (RelativeLayout) rootView.findViewById(R.id.contIncomingStream);
View remoteVideoControl = (View) LinkExtensions.getRemoteVideoControl(conference.getLink(peerID));
localMedia = new LocalMedia(this);
localMedia.getLayoutManager().addRemoteVideoControl(peerID, remoteVideoControl);
}
catch (Exception e){
e.printStackTrace();
}
}
// Inform the caller that the communicator is ready
eventListener.communicatorReady(communicatorReady, communicatorConnected, userConnected);
}
// Add the webview to the current view
RelativeLayout contCommunicator = (RelativeLayout) rootView.findViewById(R.id.contCommunicator);
contCommunicator.addView(webComm);
return rootView;
}
public void onDestroyView(){
RelativeLayout container = (RelativeLayout) getView().findViewById(R.id.contCommunicator);
container.removeView(webComm);
// Save connection state info
communicatorEngine.setCommunicatorReady(communicatorReady);
communicatorEngine.setCommunicatorConnected(communicatorConnected);
communicatorEngine.setUserConnected(userConnected);
super.onDestroyView();
}
}
Singleton:
public final class CommunicatorEngine{
private static CommunicatorEngine instance = null;
// Communicator stuff
private WebView webALC = null;
private boolean webALCInitialised = false;
private boolean communicatorReady = false;
private boolean communicatorConnected = false;
private boolean userConnected = false;
private Conference conference = null;
private String conferencePeerId = null;
private CommunicatorEngine(Context context){
// Create communicator webview
webALC = new WebView(context);
webALCInitialised = false;
}
public static synchronized CommunicatorEngine getInstance(Context context){
if(instance == null){
// Use app context to prevent memory leaks
instance = new CommunicatorEngine(context.getApplicationContext());
}
return instance;
}
public WebView getWebALC() {return webALC;}
public boolean isWebALCInitialised() {return webALCInitialised;}
public void setWebALCInitialised(boolean isInitialised) {webALCInitialised = isInitialised;}
public boolean isCommunicatorReady(){return communicatorReady;}
public void setCommunicatorReady(boolean isReady){communicatorReady = isReady;}
public boolean isCommunicatorConnected(){return communicatorConnected;}
public void setCommunicatorConnected(boolean isConnected){communicatorConnected = isConnected;}
public boolean isUserConnected(){return userConnected;}
public void setUserConnected(boolean isConnected){userConnected = isConnected;}
public Conference getConference() {return conference;}
public void setConference(Conference conferenceToSave) {conference = conferenceToSave;}
public String getConferencePeerId() {return conferencePeerId;}
public void setConferencePeerId(String peerIdToSave) {conferencePeerId = peerIdToSave;}
}
Edit
Adding log messages produces this:
Fragment﹕ ********* onDestroyView() *********
Fragment﹕ Saved communicatorReady true
Fragment﹕ Saved communicatorConnected true
Fragment﹕ Saved userConnected false
Fragment﹕ ********* onCreateView() *********
Fragment﹕ Loaded communicatorReady true
Fragment﹕ Loaded communicatorConnected true
Fragment﹕ Loaded userConnected false
Activity﹕ communicatorReady Event
Activity﹕ *******************
Activity﹕ Passed as parameter: communicatorReady true
Activity﹕ Passed as parameter: communicatorConnected true
Activity﹕ Passed as parameter: userConnected false
Activity﹕ ********************************
Activity﹕ communicator.communicatorReady false
Activity﹕ communicator.communicatorConnected false
Activity﹕ communicator.userConnected false
Edit 2
The problem goes away if I set the communicator as static:
private static Communicator communicator;
I guess it's an issue with the wrong object being queried.
Related
I have a fragment which displays a popup when the user is successfully logged in. If I navigate to a new fragment and come back, the popup with the previous username is shown again. I fixed this problem using SingleLiveEvent, but I now have to refactor my code to use MediatorLiveData as my data can come from 2 sources (remote and database), and it is not compatible with SingleLiveEvent.
I tried using an event wrapper and removing observers on onDestroyView() but so far nothing is working, the livedata onChanged function keeps getting called when I move back to the fragment. Here is some of my fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
binding = FragmentDashboardBinding.inflate(inflater, container, false);
binding.setLifecycleOwner(getActivity());
//Get the attendanceViewModel for registering attendance
attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
clokedInOutMember = attendanceAndMember.member;
}
showResultClockInOutPopup();
});
return binding.getRoot();
}
private void showResultClockInOutPopup() {
clockInBuilder = new AlertDialog.Builder(getActivity());
View view = getLayoutInflater().inflate(R.layout.status_clock_in_out_popup, null);
TextView responseClockInOut = view.findViewById(R.id.responseClockInOut);
Button dismissButton = view.findViewById(R.id.dismissButton);
//Setup Popup Text
if (clokedInOutMember != null) {
if (StringToBool(clokedInOutMember.is_clocked_in_temp)) {
responseClockInOut.setText("Bienvenue " + clokedInOutMember.name + ", tu es bien enregistré(e).");
} else {
responseClockInOut.setText("Désolé de te voir partir " + clokedInOutMember.name + ", à bientôt!");
}
} else {
responseClockInOut.setText("Oups, il semblerait qu'il y ait une erreur...\n Essaye à nouveau.");
}
[..SETUP ALERTDIALOG...]
//Dismiss popup
dismissButton.setOnClickListener(v -> {
clockInResultDialog.dismiss();
clockInResultPopupShowed = false;
clokedInOutMember = null;
});
clockInResultDialog.show();
clockInResultPopupShowed = true;
}
}
#Override
public void onDestroyView() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onDestroyView();
}
And here is my ViewModel, I have to use transformations as I am getting the userId from the fragment, passing to the Viewmodel which passes it to the repository for query (maybe there is a better way?):
public class AttendanceViewModel extends AndroidViewModel {
private AttendanceRepository repository = AttendanceRepository.getInstance();
public LiveData<AttendanceMemberModel> mAttendanceAndMember;
private MutableLiveData<String> mId = new MutableLiveData<>();
private MediatorLiveData<AttendanceMemberModel> mObservableAttendance = new MediatorLiveData<AttendanceMemberModel>();
{
mObservableAttendance.setValue(null);
mAttendanceAndMember = Transformations.switchMap(mId, id -> {
return repository.saveAttendance(id);
});
mObservableAttendance.addSource(mAttendanceAndMember, mObservableAttendance::setValue);
}
public AttendanceViewModel(#NonNull Application application) {
super(application);
}
public LiveData<AttendanceMemberModel> getAttendance() {
return mObservableAttendance;
}
public void setMemberId(String id) {
mId.setValue(id);
}
#Override
protected void onCleared() {
mObservableAttendance.setValue(null);
super.onCleared();
}
}
I can suggest you two ways. First create a boolean variable whether dialog is shown in Fragment and after showing dialog set it to true and before showing dialog check if dialog is shown. Second way is after showing dialog set livedata value to null and check if observer value null before showing dialog. I prefer second way.
Use anyone of them, which works and behaves according to your need.
#Override
public void onPause() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onPause();
}
#Override
public void onStop() {
attendanceViewModel.getAttendance().removeObservers(this);
super.onStop();
}
Fragment Life Cycle
Have a look at the lifecycle of the fragment, it will give you bit more idea.
Let me know if this works or not.
The best way to the same is to bind your view model in OnViewCreated meathod.
#Override
public void onActivityCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
setUpObservers();
}
private void setUpObservers() {
attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
clokedInOutMember = attendanceAndMember.member;
}
showResultClockInOutPopup();
});
}
If still it don't work kindly let me know. Thank you.
This question already has answers here:
Android "Only the original thread that created a view hierarchy can touch its views."
(33 answers)
Closed 4 years ago.
I know this is a question already asked but I am having difficulty understanding what is wrong with my code as I have not used any timers, AsyncTasks or other handlers. I am using Volley to make requests to my web service that's it.
The app runs on all emulators but once I try to run it on a device I get this error
E/AndroidRuntime: FATAL EXCEPTION: AcquireTokenRequestHandlerThread
Process: com.microsoft.graph.helpdesk, PID: 18915
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7146)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:1033)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:4971)
at android.view.View.invalidateInternal(View.java:12704)
at android.view.View.invalidate(View.java:12668)
at android.view.View.setFlags(View.java:10690)
at android.view.View.setVisibility(View.java:7136)
at com.microsoft.graph.helpdesk.SignInActivity.onSuccess(SignInActivity.java:73)
at com.microsoft.graph.helpdesk.SignInActivity.onSuccess(SignInActivity.java:35)
at com.microsoft.graph.auth.AuthenticationManager$2.onSuccess(AuthenticationManager.java:118)
at com.microsoft.graph.auth.AuthenticationManager$2.onSuccess(AuthenticationManager.java:115)
at com.microsoft.aad.adal.AcquireTokenRequest$CallbackHandler$2.run(AcquireTokenRequest.java:904)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.os.HandlerThread.run(HandlerThread.java:61)
The app starts and allows me to log in but then crashes immediately with the error above.
This is the code for my sign in activity:
public class SignInActivity
extends BaseActivity
implements AuthenticationCallback<AuthenticationResult> {
private FirebaseAnalytics mFirebaseAnalytics;
#InjectView(layout_diagnostics)
protected View mDiagnosticsLayout;
#InjectView(view_diagnosticsdata)
protected TextView mDiagnosticsTxt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(activity_signin);
onSignInO365Clicked();
setTitle(R.string.app_name);
// Obtain the FirebaseAnalytics instance.
mFirebaseAnalytics = FirebaseAnalytics.getInstance(this);
ButterKnife.inject(this);
}
#OnClick(o365_signin)
public void onSignInO365Clicked() {
try {
authenticate();
} catch (IllegalArgumentException e) {
warnBadClient();
}
}
#Override
public void onSuccess(AuthenticationResult authenticationResult) {
// reset anything that may have gone wrong...
mDiagnosticsLayout.setVisibility(INVISIBLE);
mDiagnosticsTxt.setText("");
// get rid of this Activity so that users can't 'back' into it
finish();
// save our auth token to use later
SharedPrefsUtil.persistAuthToken(authenticationResult);
// get the user display name
final String userDisplayableId =
authenticationResult
.getUserInfo()
.getDisplayableId();
// get the index of their '#' in the name (to determine domain)
final int at = userDisplayableId.indexOf("#");
// parse-out the tenant
final String tenant = userDisplayableId.substring(at + 1);
SharedPrefsUtil.persistUserTenant(tenant);
SharedPrefsUtil.persistUserID(authenticationResult);
// go to our main activity
start();
}
#Override
public void onError(Exception e) {
e.printStackTrace();
//Show the localized message supplied with the exception or
//or a default message from the string resources if a
//localized message cannot be obtained
String msg;
if (null == (msg = e.getLocalizedMessage())) {
msg = getString(sign_in_err);
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
} else {
mDiagnosticsTxt.setText(msg);
mDiagnosticsLayout.setVisibility(VISIBLE);
}
}
private void warnBadClient() {
Toast.makeText(this,
warning_client_id_redirect_uri_incorrect,
Toast.LENGTH_LONG)
.show();
}
private void authenticate() throws IllegalArgumentException {
validateOrganizationArgs();
mAuthenticationManager.connect(this);
}
private void validateOrganizationArgs() throws IllegalArgumentException {
UUID.fromString(ServiceConstants.CLIENT_ID);
URI.create(ServiceConstants.REDIRECT_URI);
}
private void start() {
Intent appLaunch = new Intent(this, MainActivity.class);
startActivity(appLaunch);
}
}
and also for the activity I am trying to view:
public class MainActivity extends AzureAppCompatActivity {
// Instantiate the Firebase Analytics
private FirebaseAnalytics mFirebaseAnalytics;
private TextView txtRequest;
// Instantiate the RequestQueue.
private RequestQueue mQueue;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_myTickets:
//Fragment code
setTitle("My Tickets");
MyTicketFragment mtf = new MyTicketFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content, mtf, "FragmentName");
fragmentTransaction.commit();
return true;
case R.id.navigation_closed:
//Fragment code
setTitle("Closed Tickets");
ClosedFragment cf = new ClosedFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction2 = getSupportFragmentManager().beginTransaction();
fragmentTransaction2.replace(R.id.content, cf, "FragmentName");
fragmentTransaction2.commit();
return true;
case R.id.navigation_unassigned:
//Fragment code
setTitle("Unassigned Tickets");
UnassignedFragment uf = new UnassignedFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction3 = getSupportFragmentManager().beginTransaction();
fragmentTransaction3.replace(R.id.content, uf, "FragmentName");
fragmentTransaction3.commit();
return true;
case R.id.navigation_over10days:
//Fragment code
setTitle("Tickets over 10 days");
Over10DayFragment of = new Over10DayFragment();
android.support.v4.app.FragmentTransaction fragmentTransaction4 = getSupportFragmentManager().beginTransaction();
fragmentTransaction4.replace(R.id.content, of, "FragmentName");
fragmentTransaction4.commit();
return true;
}
return false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
if (savedInstanceState == null) {
navigation.getMenu().performIdentifierAction(R.id.navigation_myTickets, 0);
}
}
#Override
protected AzureADModule getAzureADModule() {
AzureADModule.Builder builder = new AzureADModule.Builder(this);
builder.validateAuthority(true)
.authenticationResourceId(ServiceConstants.AUTHENTICATION_RESOURCE_ID)
.authorityUrl(ServiceConstants.AUTHORITY_URL)
.redirectUri(ServiceConstants.REDIRECT_URI)
.clientId(ServiceConstants.CLIENT_ID);
return builder.build();
}
#Override
protected Object[] getModules() {
return new Object[]{new AzureModule()};
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.snippet_list_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.AddTicket:
Intent AddTicketIntent = new Intent(MainActivity.this,
AddTicket.class);
startActivity(AddTicketIntent);
return true;
case R.id.disconnect:
//SignOut Needs to go here
// drop the application shared preferences to clear any old auth tokens
getSharedPreferences(AppModule.PREFS, Context.MODE_PRIVATE)
.edit() // get the editor
.clear() // clear it
.apply(); // asynchronously apply
mAuthenticationManager.disconnect();
Intent login = new Intent(this, SignInActivity.class);
login.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(login);
default:
return super.onOptionsItemSelected(item);
}
}
}
I'm not sure if they are helpful in finding an answer or if the fragments the activity is creating are the problem but I can't see how. Also I have gotten the app to work on a mobile twice before but immediately crashes once I open it again. Many thanks for your help. -Adam.
The authentication manager is handling authentication in a background thread and is calling onSuccess on the same thread. You will need to move back to the main thread to manipulate the view (set visibility, text, etc.)
I'm using two buttons with the same id in two different layouts in my app where when the first one is clicked, the app loads the 2nd layout and when the button with the same id in the 2nd layout gets clicked, it loads the first layout file. However, my issue is that this toggling happens only once and after that the button doesn't do anything. Do you have any idea on how i can call these onClickListeners whenever each button is clicked until the user leaves that activity?
CardViewActivity.java:
public class CardViewActivity extends AppCompatActivity {
private ImageView cardArtImageView;
private TextView leaderSkillDescText;
private TextView superAttackTitleText;
private TextView superAttackDescText;
private TextView passiveSkillTitleText;
private TextView passiveSkillDescText;
private TextView hpText;
private TextView attText;
private TextView defText;
private TextView costText;
private Button arrowButton;
private int selectedItemPosition;
private boolean isBtnClicked = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.cardview_refined);
// Retrieving the data sent over from MainActivity
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null) {
selectedItemPosition = bundle.getInt("Card Index");
}
//Toast.makeText(this, "WIDTH: " + SCREEN_WIDTH, Toast.LENGTH_SHORT).show();
// Initializing our views
cardArtImageView = findViewById(R.id.cardArtImageView);
viewDefinitions(false);
setSelectedViewsInit();
initCardViewData(selectedItemPosition);
arrowButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
isBtnClicked = !isBtnClicked;
if (isBtnClicked) {
setContentView(R.layout.cardview_expand_details);
viewDefinitions(true);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
Log.d("BTN", "Btn Clicked 1st time");
arrowButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
setContentView(R.layout.cardview_refined);
cardArtImageView = findViewById(R.id.cardArtImageView);
viewDefinitions(false);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
isBtnClicked = !isBtnClicked;
Log.d("BTN", "Btn Clicked 2nd time");
}
});
}
}
});
}
/**
* Sets the required textViews as selected to allow automatic scrolling
*/
private void setSelectedViewsInit() {
leaderSkillDescText.setSelected(true);
superAttackTitleText.setSelected(true);
superAttackDescText.setSelected(true);
if (passiveSkillTitleText != null && passiveSkillDescText != null) {
passiveSkillTitleText.setSelected(true);
passiveSkillDescText.setSelected(true);
}
}
/**
* Adds the views's definitions
*
* #param initPassiveInfo used to decide whether or not the passiveSkillDesc & ..Title != null
* so that they can be defined
*/
private void viewDefinitions(boolean initPassiveInfo) {
leaderSkillDescText = findViewById(R.id.leaderSkillDesc);
superAttackTitleText = findViewById(R.id.superAttackTitle);
superAttackDescText = findViewById(R.id.superAttackDesc);
if (initPassiveInfo) {
passiveSkillTitleText = findViewById(R.id.passiveSkillTitle);
passiveSkillDescText = findViewById(R.id.passiveSkillDesc);
} else {
Log.d("Definitions", "Passive info == null");
}
hpText = findViewById(R.id.HP);
attText = findViewById(R.id.ATT);
defText = findViewById(R.id.DEF);
costText = findViewById(R.id.COST);
arrowButton = findViewById(R.id.arrowButton);
}
/**
* Initialize the cardViewActivity's views with the data from the CardInfoDatabase.java class
*
* #param selectedItemPosition Used to initialize this activity's views if the intent was called from the MainScreen Fragment
*/
private void initCardViewData(int selectedItemPosition) {
if (cardArtImageView != null) {
cardArtImageView.setImageResource(CardInfoDatabase.cardArts[selectedItemPosition]);
}
leaderSkillDescText.setText(CardInfoDatabase.leaderSkills[selectedItemPosition]);
superAttackTitleText.setText(CardInfoDatabase.superAttacksName[selectedItemPosition]);
superAttackDescText.setText(CardInfoDatabase.superAttacksDesc[selectedItemPosition]);
if (passiveSkillTitleText != null && passiveSkillDescText != null) {
passiveSkillTitleText.setText(CardInfoDatabase.passiveSkillsName[selectedItemPosition]);
passiveSkillDescText.setText(CardInfoDatabase.passiveSkillsDesc[selectedItemPosition]);
}
hpText.setText(CardInfoDatabase.hp[selectedItemPosition].toString());
attText.setText(CardInfoDatabase.att[selectedItemPosition].toString());
defText.setText(CardInfoDatabase.def[selectedItemPosition].toString());
costText.setText(CardInfoDatabase.cost[selectedItemPosition].toString());
}
}
To avoid this issue, you need to make sure that the OnClickListener you assign to the button always sets the OnClickListener for the button in the "new" layout.
I haven't tested this, but it seems like it should work in theory. Try defining the listener as a private member of your class, then setting it in your onCreate, like arrowButton.setOnClickListener(arrowClickListener);:
private void arrowClickListener = new View.OnClickListener(){
#Override
public void onClick(View view) {
// clicked buttton -- pick layout based on button "state"
int resId = isBtnClicked ? R.layout.cardview_expand_details : R.layout.cardview_refined;
// set the contentview with the layout we determined earlier
setContentView(resId);
// If we're in the "normal" view, find the card art view and set our field to it
if (!isBtnClicked){
cardArtImageView = findViewById(R.id.cardArtImageView);
}
// do other initialization stuff
viewDefinitions(isBtnClicked);
initCardViewData(selectedItemPosition);
setSelectedViewsInit();
// set our new arrow button click listener to this listener
arrowButton.setOnClickListener(arrowClickListener);
// toggle button flag
isBtnClicked = !isBtnClicked;
}
}
Sorry if I got some of the logic wrong -- the key in this case is to set the click listener "recursively", in a manner of speaking, which ensures that a listener gets set after every click.
Good day all,
I have an issue where my activity is making a network call and when the network call is completed, it makes some changes in the activity using the data from the JSON object received from the call, it then passes the object down to the fragments in the same activity. These fragments are in a TabLayout.
I had this same issue which I asked here at this SO Question That sorted it out but I seem to be having the same issue, even after it worked for a little bit after not changing anything significant. I was just adding more fields I wanted to change?
The issue I have is that if I put a System.out.println() it prints out the correct data. The minute I want to set say a TextView with the data I receive in the Fragment the app Crashes with Nullpointer. When I debug it with the Debug in Android studio, the TextView I'm setting is always null for some reason.
Activity Code that does the initial Network call:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listings);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
handleIntent(getIntent());
}
private void handleIntent(Intent aIntent) {
if (aIntent != null) {
String tradeType = aIntent.getStringExtra("itemType");
String tradeId = aIntent.getStringExtra("itemId");
presenter = new ItemPresenterImpl(this, ItemBuyNowActivity.this);
presenter.doListingServiceCall(tradeId); // <------- This is the where I send the Trade Id so I can do the network call.
} else {
System.out.println("Intent is null in " + ItemBuyNowActivity.class.getSimpleName());
}
}
Interface between Activity and Presenter:
public interface ItemPresenter {
void doListingServiceCall(String itemId); //<------- Comes to this Interface
void doToolbarBackgroundImageCall(TradeItem aTradeItem);
}
Class the implements the Presenter:
#Override
public void doListingServiceCall(String aItemId) { // <------- This is where the network call starts
String homeURL = BobeApplication.getInstance().getWsURL() + mContext.getString(R.string.ws_url_item) + aItemId;
BobeJSONRequest jsObjRequest = new BobeJSONRequest(Request.Method.GET, homeURL, null, this, this);
VolleySingleton.getInstance().addToRequestQueue(jsObjRequest, "ListingRequest");
}
#Override
public void doToolbarBackgroundImageCall(TradeItem aTradeItem) {
ImageRequest request = new ImageRequest(aTradeItem.getItem().getImageUrl(),
new Response.Listener<Bitmap>() {
#Override
public void onResponse(Bitmap bitmap) {
Drawable drawable = new BitmapDrawable(mContext.getResources(), bitmap);
mItemView.loadBackgroundImage(drawable);
}
}, 0, 0, null,
new Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
mItemView.displayErrorMessage(VolleyErrorHelper.getErrorType(error, mContext) + " occurred downloading background image");
}
});
VolleySingleton.getInstance().addToRequestQueue(request, "ListItemToolbarBackgroundImageRequest");
}
#Override
public void onResponse(Object response) {
Gson gson = new Gson();
TradeItem tradeItem = gson.fromJson(response.toString(), TradeItem.class);
mItemView.populateListViews(tradeItem); // <------- This is the where I send the Object so the views in the activity can be manipulated
doToolbarBackgroundImageCall(tradeItem);
}
Method in the Activity that handles
#Override
public void populateListViews(TradeItem aTradeItem) {
mOverviewPresenter = new OverviewPresenterImpl(new OverviewListItemFragment(), aTradeItem);
OverviewListItemFragment.setData(aTradeItem); //<------- This is the where I send the Object to the fragment so i can manipulate the views in the fragment
}
class TabAdapter extends FragmentPagerAdapter {
public TabAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = new OverviewListItemFragment();
}
if (position == 1) {
fragment = new DescriptionListItemFragment();
}
if (position == 2) {
fragment = new ShippingListItemFragment();
}
if (position == 3) {
fragment = new PaymentListItemFragment();
}
return fragment;
}
#Override
public int getCount() {
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) {
return "Overview";
}
if (position == 1) {
return "Description";
}
if (position == 2) {
return "Shipping";
}
if (position == 3) {
return "Payment";
}
return null;
}
}
The Fragment that receives the data:
public class OverviewListItemFragment extends Fragment implements OverviewView {
private static TextView mOverViewHeading;
public OverviewListItemFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.overview_list_item_fragment, container, false);
mOverViewHeading = (TextView) view.findViewById(R.id.frag_overview_heading_textview);
return view;
}
#Override
public void populateOverviewViews(final TradeItem aTradeItem) {
System.out.println("Overview Trade Object title is:" + aTradeItem.getItem().getTradeTitle()); // <------- This is print statement works 100% but when I try setting mOverViewHeading to the text in aTradeItem.getItem().getTradeTitle() I get a Null pointer Exception.
}
public static void setData(TradeItem aTradeItem) {
System.out.println("Overview Trade Object title is:" + aTradeItem.getItem().getTradeTitle()); // <------- This is print statement works 100% but when I try setting mOverViewHeading to the text in aTradeItem.getItem().getTradeTitle() I get a Null pointer Exception.
mOverViewHeading.setText(aTradeItem.getItem().getTradeTitle());// <------- This is where it crashes and mOverViewHeading is still null at this point.
}
}
EDIT: Sorry I forgot the LogCat:
02-05 17:08:21.554 30512-30512/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException
at com.example.app.ui.fragments.OverviewListItemFragment.setData(OverviewListItemFragment.java:46)
at com.example.app.ui.activities.ItemBuyNowActivity.populateListViews(ItemBuyNowActivity.java:95)
at com.example.app.listing.ItemPresenterImpl.onResponse(ItemPresenterImpl.java:62)
at com.android.volley.toolbox.JsonRequest.deliverResponse(JsonRequest.java:65)
at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
My thinking is that the view I'm trying to set isn't "Active" (if thats the right word) at the time it receives the data, because when I run the debugger with a break point at the method that receives the data in the Fragment, the mOverViewHeading TextView id is null, even though I have the findViewById in the onCreate, also tried placing it in the onCreateView() but both times failed. I also tried placing the findViewById in the same method that gets called when the response is successful but before I try setting the setText() on the TextView.
Thank you
OverviewListItemFragment I assume this is not your added fragment instance, but the class.
I suggest the following changes: remove static from setData and your TextView, leave it, if you really know how it works. I don't think it is necessary or recommendable.
private OverviewListItemFragment mFrag; //declare globally
mFrag = new OverviewListItemFragment();
//if you do not want to add it now, ignore the following line
getSupportFragmentManager().beginTransaction().add(R.id.yourContainer, mFrag, "mFrag").commit();
now call mFrag.setData everytime you want to set your data. Check if your mFrag is null, then reinitialize, and maybe re-add, or whatever you want to do.
Edit: Now that I know that you use a ViewPager, I suggest the following:
Do the above. I don't think it is recommendable to have static methods in this Context. You get an error because you are trying to reach a TextView in your Fragment. This was initialized in a ViewPager/PagerAdapter, and the PagerAdapter holds the reference to the used instance of your fragment.
You can access your used fragment through
Fragment mFragment = pagerAdapter.getFragment(0); //frag at position 0
with some casting, you will be able to find your (now NOT static) method:
((OverviewListItemFragment)pagerAdapter.getFragment(0)).setData(YOUR_DATA);
Please add some try/catch. check if your fragment is null, because it is possible that your fragment is recycled in the FragmentPagerAdapter, because it reached the offset. Another way to achieve this, would be to store your required data, and update it everytime your fragment gets visible as described here.
Edit 2: Obviously, You'll need some changed in your Adapter:
I would recommend creating an array containing your fragment in the constructor:
//global in your adapter:
private Fragment[] fragments;
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
fragments = new GameFragment[4];
fragments[0] = new MyFragment();
fragments[1] = new SecondFragment();
....
}
public Fragment getItem(int position) {
return fragments[position];
}
public Fragment getFragment(int position) {
return fragments[position];
}
I've been looking for a similar problem to mine in order to find a solution, but I seriously couldn't find anything like that.
I was trying to download from parse an array of posts with an asynctask class, and after it gets the posts, it suppose to set the posts array in my page, and perform the setAdapter function in order to set my new posts array.
the problem is, after I've initialized listView and listAdapter in my home fragment,and then I perform the postArray taking from parse function, after it finishes taking the posts array from parse, it cannot update listAdapter because it says the listAdapter and my listView "haven't initialized yet", even though they have.
p.s.
sorry for not posting my code in a convenient way, I don't tend to post my code problems that often.
here's my code:
my home fragment:
public class HomeFragment extends Fragment {
View root;
ArrayList<PostClass> postsArrayList = new ArrayList<>();
static boolean isPostsArrayUpdated = false;
ListAdapter listAdapter;
PullToRefreshListView listView;
public void updatePostsArrayList(ArrayList<PostClass> postsArrayList){
if(!isPostsArrayUpdated){
// First time updating posts array list
listAdapter = new ListAdapter(getActivity(), root);
listView = (PullToRefreshListView) root.findViewById(R.id.list_container);
this.postsArrayList = postsArrayList;
listView.setAdapter(listAdapter);
isPostsArrayUpdated = true;
root.findViewById(R.id.homeFragmentLoadingPanel).setVisibility(View.GONE);
}else{
// Have updated posts before
this.postsArrayList = postsArrayList;
listAdapter.notifyDataSetChanged();
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
root = inflater.inflate(R.layout.fragment_home, container, false);
listView = (PullToRefreshListView) root.findViewById(R.id.list_container);
listAdapter = new ListAdapter(getActivity(), root);
Home_Model.getInstance().setPostsArrayList();
return root;
}
public class ListAdapter extends BaseAdapter implements View.OnClickListener{//....}
my home model:
public class Home_Model {
Home_Model(){}
static final Home_Model instance = new Home_Model();
public static Home_Model getInstance() {
return instance;
}
public void setPostsArrayList(){
new setHomePostsArray().execute();
}
public class setHomePostsArray extends AsyncTask<Void, ArrayList<PostClass>, Void>{
ArrayList<String> followersList;
ArrayList<PostClass> postsArrayList;
#Override
protected Void doInBackground(Void... params) {
// Getting posts from parse
String userName = Parse_model.getInstance().getUserClass().get_userName();
followersList = Parse_model.getInstance().getFollowersByUserNameToString(userName);
followersList.add(userName);
postsArrayList = Parse_model.getInstance().getAllUsersPostsByFollowings(followersList);
for (PostClass currPost : postsArrayList) {
for (PostClass currLocalDBPost : LocalDBPostsArray) {
if (currPost.getObjectID().equals(currLocalDBPost.getObjectID())) {
currPost.set_postPicture(currLocalDBPost.get_postPicture());
}
}
}
//Updating home page
onProgressUpdate(postsArrayList);
// Updating local data base in new posts
//checking in local DB if there are any new posts from parse and update them
for (PostClass currPost : postsArrayList) {
boolean isPostExists = false;
for (PostClass currLocalPost : LocalDBPostsArray) {
if (currPost.getObjectID().equals(currLocalPost.getObjectID())) {
isPostExists = true;
}
}
if (!isPostExists) {
ModelSql.getInstance().addPost(currPost);
Log.e("post not exist", "adding local DB");
}
}
//updating followers list in local DB
Parse_model.getInstance().getUserClass().setFollowersArray(followersList);
ModelSql.getInstance().updateFollowersArray(currUser);
return null;
}
#Override
protected void onProgressUpdate(ArrayList<PostClass>... values) {
//pass the updated postsArrayList to home fragment
if(setPostsInHomePageDelegate!= null){
setPostsInHomePageDelegate.setPosts(values[0]);
}
}
}
public interface SetPostsInHomePage {
public void setPosts(ArrayList<PostClass> postsArrayList);
}
SetPostsInHomePage setPostsInHomePageDelegate;
public void setSetPostsInHomePageDelegate(SetPostsInHomePage setPostsInHomePageDelegate) {
this.setPostsInHomePageDelegate = setPostsInHomePageDelegate;
}
main activity:
public class MainActivity extends Activity {
static HomeFragment homeFragment = new HomeFragment();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// the home fragment has already been opened during the app opening
//...
setPostsImHomePage();
}
//...
public void setPostsImHomePage(){
Home_Model.getInstance().setSetPostsInHomePageDelegate(new Home_Model.SetPostsInHomePage() {
#Override
public void setPosts(ArrayList<PostClass> postsArrayList) {
homeFragment.updatePostsArrayList(postsArrayList);
}
});
}
}
Try to move your method setPostsImHomePage(...) from MainActivity to HomeFragmentand call it in OnCreateView before return root;.
Try initializing homeFragment in onCreate before your method call. It's also helpful to know which line(s) are giving you errors.
Obviously your fragment has no View when the result arrives.
You should properly add the fragment to the Activity using the FragmentManager, then in the Fragment's onActivityCreated() callback (which is called by the system after the Fragment has its view properly set), start your AsyncTask.