I have a TabActivity with 3 tabs. There is an async task that when run by clicking a menu item for refresh, retrieves updated data from the server. This data is stored in the controller and is accessed by all views, so that the model only needs to be loaded once.
My problem is that after the async activity runs and the model is updated, how do I signal all three tabs to update their content?
My activity
public class DashboardActivity extends TabActivity {
private ProfileModel profile;
private TabHost tabHost;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
profile = Controller.getProfile();
this.setContentView(R.layout.dashboard);
tabHost = getTabHost();
setupTab(new TextView(this), "Home", new Intent().setClass(DashboardActivity.this, HomeActivity.class));
setupTab(new TextView(this), "History", new Intent().setClass(DashboardActivity.this, PaymentsActivity.class));
setupTab(new TextView(this), "My Wallet", new Intent().setClass(DashboardActivity.this, MyWalletActivity.class));
tabHost.setCurrentTab(0);
ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);
actionBar.setTitle(profile.Name);
}
private void setupTab(final View view, final String tag, Intent content) {
View tabview = createTabView(tabHost.getContext(), tag);
TabSpec setContent = tabHost.newTabSpec(tag)
.setIndicator(tabview)
.setContent(content);
tabHost.addTab(setContent);
}
private static View createTabView(final Context context, final String text) {
View view = LayoutInflater.from(context).inflate(R.layout.tabs_bg, null);
TextView tv = (TextView) view.findViewById(R.id.tabsText);
tv.setText(text);
return view;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mainmenu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.menu_settings:
return true;
case R.id.menu_refresh:
new RefreshDashboardTask().execute();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private class RefreshDashboardTask extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute()
{
ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);
if(actionBar != null)
actionBar.setProgressBarVisibility(View.VISIBLE);
}
#Override
protected Void doInBackground(Void... params)
{
try {
profile = DataHelper.getProfile();
Controller.setProfile(profile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (HttpException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ServerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result)
{
ActionBar actionBar = (ActionBar)findViewById(R.id.actionbar);
if(actionBar != null)
actionBar.setProgressBarVisibility(View.GONE);
}
}
}
EDIT For further elaboration, here is some more code.
My controller
public class Controller extends Application {
private static Controller instance;
private static DefaultHttpClient client;
private static ProfileModel profile;
public Controller() {
instance = this;
client = new DefaultHttpClient();
profile = null;
}
public static Context getContext() {
return instance;
}
public static DefaultHttpClient getHttpContext() {
return client;
}
public static ProfileModel getProfile() {
return profile;
}
public static void setProfile(ProfileModel profile) {
Controller.profile = profile;
}
#Override
public void onCreate()
{
super.onCreate();
}
}
And one of my activities that is inside the tab view. This one is the simplest one, as it is just a single list. The home view is 2 lists, separated by headers, and the wallet view is dynamically generated lists with headers, created from a collection within a collection.
public class PaymentsActivity extends Activity {
ProfileModel profile;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.payment_history);
profile = Controller.getProfile();
ListView itemList = (ListView)this.findViewById(R.id.payment_history_list);
itemList.setTextFilterEnabled(true);
itemList.clearChoices();
itemList.setAdapter(new ItemSummaryAdapter(PaymentsActivity.this, R.layout.list_item_payment, profile.Items));
//statementList.setOnItemClickListener(clickListener);
}
}
The goal here is that the refresh button updates the data in the controller. All my views inside the tabs are updated.
UPDATED:
If I were you I would the Observer pattern. First, add a set of listeners to your Controller class:
public class Controller extends Application {
private static Controller instance;
private static DefaultHttpClient client;
private static ProfileModel profile;
private static Set<ControllerUpdateListener> updateListeners = new HashSet<ControllerUpdateListener>();
//...
public static void addListener(ControllerUpdateListener listener)
{
updateListeners.add(listener);
}
public static interface ControllerUpdateListener {
void onControllerUpdate(ProfileModel model);
}
}
Then have your individual tab activities implement ControllerUpdateListener. Finally, add the trigger to the Controller class:
public static void setProfile(ProfileModel profile) {
Controller.profile = profile;
for(ControllerUpdateListener l : updateListeners) {
l.onControllerUpdate(profile);
}
}
Don't forget to register each activity as a Listener with the Controller:
public class PaymentsActivity extends Activity implements Controller.ControllerUpdateListener {
ProfileModel profile;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.payment_history);
Controller.addListener(this); // <-- Don't forget this...
profile = Controller.getProfile();
ListView itemList = (ListView)this.findViewById(R.id.payment_history_list);
itemList.setTextFilterEnabled(true);
itemList.clearChoices();
itemList.setAdapter(new ItemSummaryAdapter(PaymentsActivity.this, R.layout.list_item_payment, profile.Items));
//statementList.setOnItemClickListener(clickListener);
}
public void onControllerUpdate(ProfileModel model) {
//update these views...
}
}
Now each of your individual tab activities should trigger their notifyDataSetChanged() (or whatever other method triggers their update) in the onControllerUpdate(ProfileModel) method.
Are you calling notifyDataSetChanged() on your adapter after refreshing the content in your views? If its a ListView then just notifying the adapter should trigger a refresh. This in fact is the only way you should be dealing with rows inside ListView.
Related
I am not experienced on android and I implemented code for Tablayout, where I have six tab and each tab has there ListView where data will come from server. I am using VolleyResponse to fetch the data but all the tab's data come at open time. I need data should be come on swipe and when user will go previous swipe data should not load again. So please guide me what should I do?
Below is my code for fragment.
private void setupViewPager(ViewPager viewPager) {
int numberOfPages=7;
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new Matches_Tab(), "Matches");
adapter.addFragment(new NewMatches_Tab(), "New matches");
adapter.addFragment(new Similar_Matchs_Tab(), "Similar Matches");
adapter.addFragment(new ShortlistTab(), "ShortListed");
adapter.addFragment(new Viewed_My_Profile(), "View My Profile");
adapter.addFragment(new ShortListedMeTab(), "ShortListed Me");
adapter.addFragment(new Photo_Request_Received(), "Photo Request Received");
viewPager.setOffscreenPageLimit(numberOfPages);
viewPager.setAdapter(adapter);
}
Fragment class
public class Matches_Tab extends Fragment{
// Session Manager Class
SessionManager session;
String email;
public String JSON_URL;
private ListView listView;
public Matches_Tab() {}
private static Matches_Tab instance;
public static synchronized Matches_Tab getInstance(Bundle data){
if (instance == null) {
instance = new Matches_Tab();
instance.setArguments(data);
}
return instance;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TypefaceUtil.overrideFont(getContext(), "SERIF", "Peddana-Regular.ttf");
// Session class instance
session = new SessionManager(getActivity());
// get user data from session
HashMap<String, String> user = session.getUserDetails();
email = user.get(SessionManager.KEY_EMAIL);
JSON_URL = "https://www.maangal.com/maangal_mobile/matches.php?matri_id="+email;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.matches_tab, container, false);
// ProgressBar progressbar = (ProgressBar) view.findViewById(R.id.progressBar);
listView = (ListView) view.findViewById(R.id.listView);
sendRequest();
//getArguments();
return view;
}
private void sendRequest(){
final ProgressDialog loading = ProgressDialog.show(getActivity(),"Loading Data", "Please wait...",false,false);
StringRequest stringRequest = new StringRequest(Request.Method.POST,JSON_URL,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
loading.dismiss();
showJSON(response);
Log.e("Broder Matches s----->",response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getContext(),error.getMessage(), Toast.LENGTH_LONG).show();
}
});
int MY_SOCKET_TIMEOUT_MS = 30000;
stringRequest.setRetryPolicy(new DefaultRetryPolicy(
MY_SOCKET_TIMEOUT_MS, DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
RequestQueue requestQueue = Volley.newRequestQueue(getContext());
requestQueue.add(stringRequest);
}
protected void showJSON(String json){
ParseJSON pj = new ParseJSON(json);
pj.parseJSON();
Profile_Match_custom_List cl = new Profile_Match_custom_List(getActivity(), ParseJSON.ids,ParseJSON.ages, ParseJSON.heights, ParseJSON.communities,ParseJSON.castes,ParseJSON.educations,ParseJSON.occupations,ParseJSON.incomes,ParseJSON.pics,ParseJSON.locations,ParseJSON.shortlist,ParseJSON.expressinterest);
listView.setAdapter(cl);
}
}
You can extend BaseFragment
public abstract class BaseFragment extends Fragment {
protected Activity mActivity;
/**
* isFragmentVisible
*/
private boolean isFragmentVisible;
/**
* View in onCreateView is ok
*/
private boolean isPrepared;
/**
* isFirstLoad
*/
private boolean isFirstLoad = true;
/**
* isForceLoad
*/
private boolean isForceLoad = false;
/**
* onAttach Activity
*
* #param context
*/
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.mActivity = (Activity) context;
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container
, Bundle savedInstanceState) {
isFirstLoad = true;
View view = LayoutInflater.from(mActivity)
.inflate(getLayoutId(), container, false);
initView(view, savedInstanceState);
isPrepared = true;
lazyLoad();
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onDestroy() {
super.onDestroy();
}
/**
* want to load data ,need set in onCreateView
* isPrepared = true;
*/
protected void lazyLoad() {
if (isPrepared() && isFragmentVisible()) {
if (isForceLoad || isFirstLoad()) {
isForceLoad = false;
isFirstLoad = false;
initData();
}
}
}
public boolean isPrepared() {
return isPrepared;
}
public boolean isFirstLoad() {
return isFirstLoad;
}
public boolean isFragmentVisible() {
return isFragmentVisible;
}
/**
* use with viewpager
*
* #param isVisibleToUser is visible for user
*/
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
onVisible();
} else {
onInvisible();
}
}
/**
* if you use show hide to set fragment ,do this
*
* #param hidden hidden True if the fragment is now hidden, false if it is not
* visible.
*/
#Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
onVisible();
} else {
onInvisible();
}
}
protected void onVisible() {
isFragmentVisible = true;
lazyLoad();
}
protected void onInvisible() {
isFragmentVisible = false;
}
/**
* set force load
*/
public void setForceLoad(boolean forceLoad) {
this.isForceLoad = forceLoad;
}
/**
* getLayoutId ,like : R.layout.fragment
*
* #return
*/
protected abstract int getLayoutId();
/**
* initView
*
* #param view
* #param savedInstanceState
*/
protected abstract void initView(View view, Bundle savedInstanceState);
/**
* load data here
*/
protected abstract void initData();
}
Then load data in initData method .
#Override
protected void initData() {
// request here
}
Note
the main method is setUserVisibleHint and onHiddenChanged
add some boolean flag to determine whether the data has been loaded, view creates etc.
You can use ViewPager.OnPageChangeListener to get callbacks for when a page becomes visible.
Check below code.
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// Add your logic
}
});
Aternate solution
tabLayout.setupWithViewPager(mViewPager);
tabLayout.setOnTabSelectedListener(
new TabLayout.ViewPagerOnTabSelectedListener(mViewPager) {
#Override
public void onTabSelected(TabLayout.Tab tab) {
super.onTabSelected(tab);
//put your Logic here
}
});
If you are using ViewPager and you are getting data for all the tabs in one API call then I suggest you set the offset of the viewpager equal to the number of tabs.
ViewPager vp = ...;
vp.setOffscreenPageLimit(numberOfTabsHERE);
NOTE: Only use this if there is not much data to show else you may get a MemoryOutOfException.
If you do have a lot of tabs and a lot of data. Using Singleton pattern for fragments is a good option.
public class MyFragment extends Fragment {
private static MyFragment instance;
public static synchronized MyFragment getInstance(Bundle data // if any){
if (instance == null) {
instance = new MyFragment();
instance.setArguments(data);
}
return instance;
}
}
...
onCreateView{.... getArguments()}
I want to convert my project according to MVP structure, & I had done it but it violate the MVP design, as it holds the activity instance in the presenter layer.
So, I just wanted to know how can I convert this project into pure MVP. Here Validation class is recursive and validate many fields & for here it is just for signup, and I had put the Validate method into separate thread.
This is my MVP interface
import android.app.Activity;
public class IMVP_Login {
/**
* View mandatory methods. Available to Presenter
* Presenter -> View
*/
public interface RequiredViewOps {
void showToast(String msg);
}
/**
* Operations offered from Presenter to View
* View -> Presenter
*/
public interface PresenterOps{
void submit(Activity activity);
}
}
This is my presenter with thread and containing the activity instance, which is against the design pattern of MVP, the code is as follows
import android.app.Activity;
import java.lang.ref.WeakReference;
import cp.utility.CustomException;
import cp.utility.Validation;
public class PresenterLogin implements Runnable,IMVP_Login.PresenterOps
{
private WeakReference<IMVP_Login.RequiredViewOps> mView;
// this is against the architectural law of MVP
private WeakReference<Activity> activity;
public PresenterLogin(IMVP_Login.RequiredViewOps mView) {
this.mView = new WeakReference<>(mView);
}
#Override
public void run() {
try
{
Validation.validate(activity.get());
}catch (CustomException e)
{
mView.get().showToast(e.getMessage());
}
}
//how should i do this with MVP PATTERN,as it is holding the activity instance
#Override
public void submit(Activity activity) {
this.activity=new WeakReference<>(activity);
Thread validationThread = new Thread(this,"Validation");
validationThread.start();
}
}
This is my activity,
public class Login extends AppCompatActivity implements View.OnClickListener,IMVP_Login.RequiredViewOps
{
private TextInputEditText edPhone,edCountrycode,edPassword;
private IMVP_Login.PresenterOps presenterLogin;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signin);
initialize();
}
private void initialize()
{
presenterLogin= new PresenterLogin(this);
Button btSignIn=GeneralFunction.findViewByIdAndCast(this,R.id.btnSignIn);
btSignIn.setOnClickListener(this);
edCountrycode = GeneralFunction.findViewByIdAndCast(this, R.id.etCode);
edPhone = GeneralFunction.findViewByIdAndCast(this, R.id.etPhone);
edPassword = GeneralFunction.findViewByIdAndCast(this, R.id.etPassword);
edPassword.setTypeface(Typekit.getInstance().get(getString(R.string.str_regular)));
edPassword.setTransformationMethod(new PasswordTransformationMethod());
}
#Override
public void onClick(View view) {
switch (view.getId())
{
case R.id.btnSignIn:
presenterLogin.submit(this);
break;
}
}
#Override
public void showToast(String msg) {
//show toast
}
}
This is the validation class depending on tag of editext,
public class Validation {
public static boolean validateFields(final ViewGroup parentView) throws CustomException
{
for (int i = 0; i < parentView.getChildCount(); i++)
{
if (parentView.getChildAt(i) instanceof ViewGroup) {
if ((parentView.getChildAt(i)).getVisibility() == View.VISIBLE)
validateFields((ViewGroup) parentView.getChildAt(i));
}
else if((parentView.getChildAt(i) instanceof TextView) && ((parentView.getChildAt(i)).getVisibility() == View.VISIBLE))
{
TextView editText = (TextView) parentView.getChildAt(i);
if(null!=editText.getTag())
{
String type = editText.getTag().toString().toLowerCase();
String text=GeneralFunction.getTextFromView(editText);
//validation depending on tag
}
}
}
return true;
}
public static boolean validate(Activity activity) {
final ViewGroup viewGroup = (ViewGroup) activity.findViewById(android.R.id.content);
return validateFields(viewGroup);
}
}
Let me start by saying that there are many different ways of doing MVP, each of them valid in their own right. The important things to keep in mind are:
The View should not know about the model, it doesn't care at all where its data is coming from.
The Presenter should not know about Android. You should be able to run your Presenter class entirely on the JVM.
Your Activity/Fragment/ViewGroup should implement the View interface which is how the Presenter communicates with them.
Why do we do this?
Separation of concerns.
You can change the network library you use in your Model and the View/Presenter should just work still. You could switch your View from a horizontal ViewPager to a vertical RecyclerView and the Presenter/Model equally wouldn't care.
Testing.
We can mock our Presenter and unit test the View or Model. Mock the View & Model and unit test the Presenter.
As long as the implementation of MVP that you are using allows the above then in my mind it is valid.
Onto your specific problem. I would set it up something like this:
View:
public interface LoginView {
Map<String,String> getLoginFields();
}
Activity:
public class LoginActivity extends AppCompatActivity implements LoginView {
private EditText emailView;
private EditText phoneView;
private EditText passwordView;
private Button loginView;
private LoginPresenter presenter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
presenter = new LoginPresenter();
presenter.bindView(this);
emailView = findViewById(R.id.login_email);
phoneView = findViewById(R.id.login_phone);
passwordView = findViewById(R.id.login_password);
loginView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
presenter.login();
}
});
}
#Override
protected void onDestroy() {
presenter.unbindView();
super.onDestroy();
}
#Override
public Map<String, String> getLoginFields() {
Map<String, String> fields = new HashMap<>();
fields.put(emailView.getTag().toString(), emailView.getText().toString());
fields.put(phoneView.getTag().toString(), phoneView.getText().toString());
fields.put(passwordView.getTag().toString(), passwordView.getText().toString());
return fields;
}
}
You may wish to do something fancy with the getLoginFields method and loop through your container. Even if you had 100 fields though it shouldn't require offloading onto another thread. I'd be a very upset user if I had to fill out 100 fields...
Presenter:
public class LoginPresenter {
private LoginView view;
private LoginValidator validator;
public void bindView(LoginView view) {
this.view = view;
}
public void unbindView() {
view = null;
}
public void login() {
validator = new LoginValidator();
Map<String, String> fields = view.getLoginFields();
boolean isValid = validator.validate(fields);
}
}
Validator:
public class LoginValidator {
public boolean validate(Map<String, String> fields) {
//validation depending on tag
return true;
}
}
Threading
If the need does arise to process something on another thread in the Activity then you have several approaches you could take:
Pass a listener to the getLoginFields() method which gets called when the work is done.
Expose another method in the Presenter, something like onLoginFieldsProcessed which would get called once the work is done.
Have getLoginFields() return Observable (RxJava) or Future.
I would personally probably use RxJava, especially if I was already using it in the app.
The main reason why there is separate presenter class added in this MVP framework ( especially in android ) is to remove the OutOfMemory or if by chance the activity fails the presenter calls is not affected i.e why there is MVP approach followed instead of MV framework.
Consider the below example that is from below link :-
public class MainActivity extends Activity {
public static final String DEFAULT_NAME = "Chuck Norris";
private ArrayAdapter<ServerAPI.Item> adapter;
private Subscription subscription;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter = new ArrayAdapter<>(this, R.layout.item));
requestItems(DEFAULT_NAME);
}
#Override
protected void onDestroy() {
super.onDestroy();
unsubscribe();
}
public void requestItems(String name) {
unsubscribe();
subscription = App.getServerAPI()
.getItems(name.split("\\s+")[0], name.split("\\s+")[1])
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<ServerAPI.Response>() {
#Override
public void call(ServerAPI.Response response) {
onItemsNext(response.items);
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable error) {
onItemsError(error);
}
});
}
public void onItemsNext(ServerAPI.Item[] items) {
adapter.clear();
adapter.addAll(items);
}
public void onItemsError(Throwable throwable) {
Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_LONG).show();
}
private void unsubscribe() {
if (subscription != null) {
subscription.unsubscribe();
subscription = null;
}
}
}
In the above example the activity failure causes the presenter layer to stop working .Similarly ,if there is any object associated with this activity class ( View ) will be affected .
Referencing by static will make the activity die out while there is a crash but the presenter class will not be affected.( Please refer to below code for MVP ).
public class MainPresenter {
public static final String DEFAULT_NAME = "Chuck Norris";
private ServerAPI.Item[] items;
private Throwable error;
private MainActivity view;
public MainPresenter() {
App.getServerAPI()
.getItems(DEFAULT_NAME.split("\\s+")[0], DEFAULT_NAME.split("\\s+")[1])
.delay(1, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<ServerAPI.Response>() {
#Override
public void call(ServerAPI.Response response) {
items = response.items;
publish();
}
}, new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
error = throwable;
publish();
}
});
}
public void onTakeView(MainActivity view) {
this.view = view;
publish();
}
private void publish() {
if (view != null) {
if (items != null)
view.onItemsNext(items);
else if (error != null)
view.onItemsError(error);
}
}
}
public class MainActivity extends Activity {
private ArrayAdapter<ServerAPI.Item> adapter;
private static MainPresenter presenter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView listView = (ListView)findViewById(R.id.listView);
listView.setAdapter(adapter = new ArrayAdapter<>(this, R.layout.item));
if (presenter == null)
presenter = new MainPresenter();
presenter.onTakeView(this);
}
#Override
protected void onDestroy() {
super.onDestroy();
presenter.onTakeView(null);
if (!isChangingConfigurations())
presenter = null;
}
public void onItemsNext(ServerAPI.Item[] items) {
adapter.clear();
adapter.addAll(items);
}
public void onItemsError(Throwable throwable) {
Toast.makeText(this, throwable.getMessage(), Toast.LENGTH_LONG).show();
}
}
MainActivity creates MainPresenter and keeps it outside of reach of onCreate/onDestroy cycle. MainActivity uses a static variable to reference MainPresenter, so every time a process restarts due to out-of-memory event, MainActivity should check if the presenter is still here and create it if needed.( As stated in the doc ).
Hope this helps :)
I make chat application with android and socket. Chat interface is inside the fragment. While both device show chat interface, it runs well. But when one of device close, it give me error force close.
Here is my code.
MainActivity.java
public class MainActivity extends AppCompatActivity {
private FragmentManager manager;
private android.support.v4.app.FragmentTransaction transaction;
private Socket socket;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
manager = getSupportFragmentManager();
transaction = manager.beginTransaction();
chatFragment enter = new chatFragment();
transaction.add(R.id.fragmentContainer, enter);
transaction.commit();
}
}
}
and ChatFragment.java
public class ChatFragment extends Fragment {
private ListView listchat;
private Button send;
private EditText msgText;
public ChatAdapter adapter;
private ArrayList<ChatModel> items = new ArrayList<ChatModel>();
private boolean isactive;
private Toolbar chatToolbar;
private FragmentManager manager;
private FragmentTransaction transaction;
private Socket socket;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setHasOptionsMenu(true);
View v;
v = inflater.inflate(R.layout.chat_fragment, container, false);
SocketSingleton singleton = (SocketSingleton) getActivity().getApplication();
socket = singleton.getmSocket();
// this is listener for message coming
socket.on("message", messageListener);
msgText = (EditText) v.findViewById(R.id.editMsg);
adapter = new ChatAdapter(getActivity(), R.layout.chat_list, items);
listchat = (ListView) v.findViewById(R.id.listchat);
listchat.setAdapter(adapter);
listchat.setDivider(null);
send = (Button) v.findViewById(R.id.btnSend);
send.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
addMessage();
}
});
return v;
}
private void addMessage() {
ChatModel item = new ChatModel();
item.setMsg(msgText.getText().toString());
items.add(item);
adapter.notifyDataSetChanged();
String message = msgText.getText().toString();
socket.emit("message", message);
msgText.setText("");
}
private Emitter.Listener messageListener = new Emitter.Listener() {
#Override
public void call(final Object... args) {
// error start from here
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
JSONObject json = (JSONObject) args[0];
String msg;
try {
msg = json.getString("message").toString();
ChatModel model = new ChatModel();
model.setMsg(msg);
items.add(model);
adapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
};
}
In longcat it says.
FATAL EXCEPTION: EventThread
java.lang.NullPointerException
in application, it force close when message is coming and activity close. How to handle this listener when message is coming, and activity close? Thanks.
You should check for getActivity() first .
if( getActivity() != null ){
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
JSONObject json = (JSONObject) args[0];
String msg;
try {
msg = json.getString("message").toString();
ChatModel model = new ChatModel();
model.setMsg(msg);
items.add(model);
adapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
You have to set messageListener to null in onDetach() of your fragment.
#Override
public void onDetach() {
super.onDetach();
messageListener = null;
}
I have 10 spinners and one button. By clicking button activity A is going to another activity B and I want to add the selected information from spinners to send from activity A to activity B by clicking that button and want to display selected spinners info in activity B.
Here is my code (activity A) :
public class Firstactivity extends MainActivity {
private Button btn;
private Spinner spin;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.third_layout_main);
btn= (Button)findViewById(R.id.btn1s);
spin=(Spinner)findViewById(R.id.spinner1);
final String arr[] = {"NONE"," 1*£5.49","2*2.00","3*4.00"};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(Firstactivity.this, android.R.layout.simple_dropdown_item_1line, arr);
spin.setAdapter(adapter);
spin.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
// TODO Auto-generated method stub
Toast.makeText(Firstactivity.this, "the item selected "+arr[arg2],Toast.LENGTH_SHORT).show();
}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
//TODO Auto-generated method stub
}
});
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
Intent inte = new Intent (Firstactivity.this, Finalorder.class);
String Text = spin.getSelectedItem().toString();
startActivity(inte);
}
});
}}
Activity B :
public class Finalorder extends MainActivity {
Bundle Bundle;
TextView txt;
Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.last);
Bundle=getIntent().getExtras();
btn=(Button)findViewById(R.id.btn1s);
Bundle=getIntent().getExtras();
txt.setText(Bundle.getString("SPINNERVAL"));
}
}
You need to create your custom serializable or parcelable object with all spinners values. And transfer it through activity.
Something like that:
public class SpinnersData implements Serializable
{
private String spinner1;
private String spinner2;
//....
private String spinner10;
public String getSpinner1()
{
return spinner1;
}
public void setSpinner1(String spinner1)
{
this.spinner1 = spinner1;
}
public String getSpinner2()
{
return spinner2;
}
public void setSpinner2(String spinner2)
{
this.spinner2 = spinner2;
}
public String getSpinner10()
{
return spinner10;
}
public void setSpinner10(String spinner10)
{
this.spinner10 = spinner10;
}
}
Put it to intent in one activity:
SpinnersData data = new SpinnersData();
data.setSpinner1(spinner1.getSelectedItem().toString());
data.setSpinner2(spinner2.getSelectedItem().toString());
....
yourIntent.putExtra("someKey", data);
And get in another activity in onCreate():
SpinnersData data = (SpinnersData) getIntent().getExtras().getSerializable("someKey");
Now you can use this object to display information here.
I’m using ksoap to call a web service.
When Application starts or when i scroll to end of page Application fetches data from Web Service correctly and it shows data as well as i want, But When I want to show Progress Dialog in a AsyncTask it doesn’t work correctly. It shows Progress Dialog after PostExecute in a blik of eyes. So, What's wrong in my code ? I can't understand where is my problem. ;-)
I have a web service class called ElvatraWebService:
Here is a Method witch is fetch latest post with Asynctasks
private String GetLastPublicPosts(int counter){
...
...
...
...
return resTxt; //Returns JSON String
}
public String getLatestPost(int counter, Activity a){
CallLatestPost task = new CallLatestPost(counter,a);
try {
strJSON = task.execute("getLatest").get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Log.i("sss", "--" + strJSON);
return strJSON;
}
private class CallLatestPost extends AsyncTask<String, Void, String> {
private int Counter = 0;
private ProgressDialog dialog;
private Activity activity;
private Context context;
public CallLatestPost (int c,Activity actt) {
super();
this.Counter = c;
activity = actt;
context = actt;
dialog = new ProgressDialog(this.context);
}
#Override
protected String doInBackground(String... params) {
ElvatraWebService elws = new ElvatraWebService();
String tmp = elws.GetLastPublicPosts(this.Counter);
//Log.i("strJSON",strJSON);
return tmp;
}
#Override
protected void onPostExecute(String result) {
//Set response
super.onPostExecute(result);
if (dialog.isShowing()) {
dialog.dismiss();
}
}
#Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(context);
dialog.setMessage("Connecting...");
//dialog.setIndeterminate(true);
dialog.setCancelable(true);
dialog.show();
Log.i("###Async", "=== " + "PreExec");
}
#Override
protected void onProgressUpdate(Void... values) {
}
}
I have a Class called post. It will call getLatestPost from Elvatrawebservice.
Then it will convert JSON String to JsonArray with PostAdapter class. PostAdapter is a class extends BaseAdapter.
In Main Activity I call a local Method called LoadContent in onCreate method.
Here is MainActivity Class
public class MainActivity extends Activity {
String displayText="";
public TextView tv;
public ListView lv;
public ProgressBar pg;
int theCounter=20;
String[] strTAG = new String[] {"title","photo","date","desc","linkurl"};
//int[] intField = new int[] {R.id.title,R.id.imgPost, R.id.Date, R.id.desc, R.id.link};
ListAdapter sa;
List<HashMap<String,Object>> list;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.recentposts);
lv = (ListView) findViewById(R.id.list);
lv.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount) {
//load more data
// Load content of listview
LoadContent();
}
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public void onDestroy() {
super.onDestroy(); // Always call the superclass
// Stop method tracing that the activity started during onCreate()
android.os.Debug.stopMethodTracing();
}
public void LoadContent(){
lv = (ListView) findViewById(R.id.list);
Post p = new Post(theCounter);
theCounter+=20;
if (p.GetLatestPosts(lv,MainActivity.this))
{
//tv.setText("true");
}
}
}
You have
task.execute("getLatest").get();
You should not call get() as this blocks the ui thread waiting for the result making it asynctask asynchronous no more. Just use
task.execute("getLatest");
You can return result in doInBackground and update ui in onPostExecute.