I'm working on a new project that implements MVVM. Can I use a viewmodel that is observed for two activities ? or should I make one viewmodel for each activity ?
public class FormViewModel extends AndroidViewModel {
/*
This is my only ViewModel in the project
*/
private UserRepository userRepository;
//linked fields in xml for lib Data Binding
public String name, lastName, address, age;
//variables observed in the views
public MutableLiveData<String> responseMessageInsertUpdate = new MutableLiveData<>();
public MutableLiveData<String> responseStartUserFormActivity = new MutableLiveData<>();
public MutableLiveData<String> responseMessageDelete = new MutableLiveData<>();
public FormViewModel(Application application) {
super(application);
userRepository = new UserRepository(application);
}
//get all users from database that implements RoomDataBase, it´s observed em MainActivity
//and update recyclerview when database receive any change
public LiveData<List<User>> getAllUsers() {
return userRepository.selectAllUsers();
}
/*
action of submit button defined (linked for lib Data Binding) in xml
makes change or user registration
*/
public void submitClick(User user) {
int idade = 0;
if (this.age != null) {
if (!this.age.isEmpty()) {
idade = Integer.parseInt(this.age);
}
}
if (user != null) {
user.setName(name);
user.setLastName(lastName);
user.setAddress(address);
user.setAge(idade);
} else {
user = new User(name, lastName, address, idade);
}
//validation logic
if (user.isFormValid()) {
if (user.getId() > 0) {
//update the user in the database
userRepository.updateUser(user);
//there is an observable of this MutableLiveData variable in UserFormActivity that shows this
//message in a toast for the User when received a value
responseMessageInsertUpdate.setValue("User data uploaded successfully.");
} else {
//insert the user on data base
userRepository.insertUser(user);
responseMessageInsertUpdate.setValue("User " + user.getName() + " stored successfully.");
}
} else {
responseMessageInsertUpdate.setValue("Please, correctly fill in all the fields of the form to confirm the registration.");
}
}
//action of btnNewForm linked for lib Data Binding in xml
public void newFormClick() {
/*
this MutableLiveData is observed for MainActivity and start a new UserFormActivity when receive
value when the btnNewForm is pressed
*/
responseStartUserFormActivity.setValue("startActivity");
}
//delete User from database
public void deleteUser(User user) {
if (user != null) {
userRepository.deleteUser(user);
/*
there is an observable of this MutableLiveData variable in MainActivity that shows this
message in a toast for the user when received a value (when an user is deleted from database)
*/
responseMessageDelete.setValue(user.getName() + " removed from list successfully.");
}
}
//this method is called on UserFormActivity to show more details of an existing user in activity fields
public void showDataUserInActivity(User user) {
//linked fields in xml for lib Data Binding that receive values from the object user
name = user.getName();
lastName = user.getLastName();
address = user.getAddress();
age = String.valueOf(user.getAge());
}
}
public class MainActivity extends AppCompatActivity {
/*
this activity shows all users in recyclerview
*/
private Context contexto = this;
private ActivityMainBinding binding;
private UserAdapter userAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
FormViewModel formViewModel = ViewModelProviders.of(this).get(FormViewModel.class);
binding.setViewModel(formViewModel);
createRecyclerView();
methodsViewModel();
}
//methods from ViewModel
private void methodsViewModel() {
//observer that update recyclerview when database receive any change
binding.getViewModel().getAllUsers().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(#Nullable List<User> pessoas) {
userAdapter.addUserToList(pessoas);
}
});
//observer that starts a new UserFormActivity when btnNewForm is pressed
//receive value in the method newFormClick from ViewModel
binding.getViewModel().responseStartUserFormActivity.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
startUserFormActivity();
}
});
//observer that shows a message in a toast when the user is deleted from database
//receive value in the method deleteUser from ViewModel
binding.getViewModel().responseMessageDelete.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String message) {
Toast.makeText(contexto, message, Toast.LENGTH_SHORT).show();
}
});
}
private void createRecyclerView() {
RecyclerView rvUser = binding.rvPessoas;
rvUser.setLayoutManager(new LinearLayoutManager(contexto));
userAdapter = new UserAdapter(contexto, itemClick());
rvUser.setAdapter(userAdapter);
}
private void startUserFormActivity() {
Intent intent = new Intent(contexto, UserFormActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
contexto.startActivity(intent);
}
private void startUserFormActivity(User user) {
Intent intent = new Intent(contexto, UserFormActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra("user", user);
contexto.startActivity(intent);
}
private UserAdapter.ItemClick itemClick() {
return new UserAdapter.ItemClick() {
#Override
public void simpleClick(View view, final int position) {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(contexto);
String[] options = {"Update", "Delete"};
alertDialog.setItems(options, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
if (i == 0) {
//start a new UserFormActivity to change user attributes
startUserFormActivity(userAdapter.getUserFromList().get(position));
} else if (i == 1) {
//call the method deleteUser from ViewModel
binding.getViewModel().deleteUser(userAdapter.getUserFromList().get(position));
}
}
});
alertDialog.show();
}
};
}
}
public class UserFormActivity extends AppCompatActivity {
private Context context = this;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FormViewModel formViewModel = ViewModelProviders.of(this).get(FormViewModel.class);
final ActivityFormUserBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_form_user);
binding.setViewModel(formViewModel);
if (getIntent().getSerializableExtra("user") != null) {
User user = (User) getIntent().getSerializableExtra("user");
formViewModel.showDataUserInActivity(user);
//put user data in activity when action "update" is called in MainActivity
binding.setUser(user);
}
/*
Method from ViewModel
Observer that shows a message in a toast and close the activity when the user is storage or updated from database
receive value in the method submitClick from ViewModel
*/
formViewModel.responseMessageInsertUpdate.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
Toast.makeText(context, s, Toast.LENGTH_LONG).show();
if (s.contains("successfully")) {
finish();
}
}
});
}
}
Here is my ViewModel and my two activities for more details. As I said it's a ViewModel that is observed for two activities. This ViewModel calls a repository that updates, inserts and deletes user data as well as also updates e sends messages to the views.
It's completely OK to share a viewmodel among the views, in case if you're using the same data or it's a kind of centralised datastore.
Otherwise implement separate model for each view as it increases
code readability and hence efficiency.
Happy to provide personalised solution if you could post some of your
code snippets here. Happy coding
Related
I've been stuck in a situation and i need some help over here. There are many articles on this topic here but none of them answered my question. I want to implement onBackPressed() in fragments and show dialog box which shows to exit the application or not. Any help would be appreciated.
LoginFragment.java
public class LoginFragment extends Fragment {
public static final String TAG = LoginFragment.class.getSimpleName();
private EditText mEtEmail;
private EditText mEtPassword;
private Button mBtLogin;
private TextView mTvRegister;
private TextView mTvForgotPassword;
private TextInputLayout mTiEmail;
private TextInputLayout mTiPassword;
private ProgressBar mProgressBar;
private CompositeSubscription mSubscriptions;
private SharedPreferences mSharedPreferences;
#NonNull
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_login,container,false);
mSubscriptions = new CompositeSubscription();
initViews(view);
initSharedPreferences();
return view;
}
private void initViews(View v) {
mEtEmail = v.findViewById(R.id.et_email);
mEtPassword = v.findViewById(R.id.et_password);
mBtLogin = v.findViewById(R.id.btn_login);
mTiEmail = v.findViewById(R.id.ti_email);
mTiPassword = v.findViewById(R.id.ti_password);
mProgressBar = v.findViewById(R.id.progress);
mTvRegister = v.findViewById(R.id.tv_register);
mTvForgotPassword = v.findViewById(R.id.tv_forgot_password);
mBtLogin.setOnClickListener(view -> login());
mTvRegister.setOnClickListener(view -> goToRegister());
mTvForgotPassword.setOnClickListener(view -> showDialog());
}
private void initSharedPreferences() {
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
}
private void login() {
setError();
String email = mEtEmail.getText().toString();
String password = mEtPassword.getText().toString();
int err = 0;
if (!validateEmail(email)) {
err++;
mTiEmail.setError("Email should be valid !");
}
if (!validateFields(password)) {
err++;
mTiPassword.setError("Password should not be empty !");
}
if (err == 0) {
loginProcess(email,password);
mProgressBar.setVisibility(View.VISIBLE);
} else {
showSnackBarMessage("Enter Valid Details !");
}
}
private void setError() {
mTiEmail.setError(null);
mTiPassword.setError(null);
}
private void loginProcess(String email, String password) {
mSubscriptions.add(NetworkUtil.getRetrofit(email, password).login()
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse,this::handleError));
}
private void handleResponse(Response response) {
mProgressBar.setVisibility(View.GONE);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.TOKEN,response.getToken());
editor.putString(Constants.EMAIL,response.getMessage());
editor.apply();
mEtEmail.setText(null);
mEtPassword.setText(null);
Intent intent = new Intent(getActivity(), HomeActivity.class);
startActivity(intent);
}
private void handleError(Throwable error) {
mProgressBar.setVisibility(View.GONE);
if (error instanceof HttpException) {
Gson gson = new GsonBuilder().create();
try {
String errorBody = ((HttpException) error).response().errorBody().string();
Response response = gson.fromJson(errorBody,Response.class);
showSnackBarMessage(response.getMessage());
} catch (IOException e) {
e.printStackTrace();
}
} else {
showSnackBarMessage("No Internet Connection!");
}
}
private void showSnackBarMessage(String message) {
if (getView() != null) {
Snackbar.make(getView(),message,Snackbar.LENGTH_SHORT).show();
}
}
private void goToRegister(){
FragmentTransaction ft = getFragmentManager().beginTransaction();
RegisterFragment fragment = new RegisterFragment();
ft.replace(R.id.fragmentFrame,fragment,RegisterFragment.TAG);
ft.addToBackStack(null).commit();
}
private void showDialog(){
ResetPasswordDialog fragment = new ResetPasswordDialog();
fragment.show(getFragmentManager(), ResetPasswordDialog.TAG);
}
#Override
public void onDestroy() {
super.onDestroy();
mSubscriptions.unsubscribe();
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity implements ResetPasswordDialog.Listener {
public static final String TAG = MainActivity.class.getSimpleName();
private LoginFragment mLoginFragment;
private ResetPasswordDialog mResetPasswordDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
loadFragment();
}
}
private void loadFragment() {
if (mLoginFragment == null) {
mLoginFragment = new LoginFragment();
}
getFragmentManager().beginTransaction().replace(R.id.fragmentFrame, mLoginFragment, LoginFragment.TAG).commit();
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String data = intent.getData().getLastPathSegment();
Log.d(TAG, "onNewIntent: " + data);
mResetPasswordDialog = (ResetPasswordDialog) getFragmentManager().findFragmentByTag(ResetPasswordDialog.TAG);
if (mResetPasswordDialog != null)
mResetPasswordDialog.setToken(data);
}
#Override
public void onPasswordReset(String message) {
showSnackBarMessage(message);
}
private void showSnackBarMessage(String message) {
Snackbar.make(findViewById(R.id.activity_main), message, Snackbar.LENGTH_SHORT).show();
}
}
In My Login Fragment, I want to show a dialog box "Do you want to exit the application or not". On Yes it dismiss the current fragment and end the activity otherwise it'll remain active. Help please!
You can even try this way
MainActivity.java
#Override
public void onBackPressed() {
if (getFragmentManager() != null && getFragmentManager().getBackStackEntryCount() >= 1) {
String fragmentTag = getFragmentManager().findFragmentById(R.id.frame_container).getTag();
if(fragmentTag.equals(LoginFragment.getTag())){
// show Dialog code
}else{
super.onBackPressed();
}
} else {
super.onBackPressed();
}
}
Add this code in your main activity so that when login fragment is added and you click backpress, then on first if the fragment is added to fragment transaction, then first it finds the fragment and check if its tag is equals to the login fragment tag. Then if both tag matches, then you can show your exit alert dialog.
Android team has prepared a new way of handling the back button pressed on Fragments for us, so you should check this out. It's called OnBackPressedDispatcher.
You need to register OnBackPressedCallback to the fragment where do you want to intercept back button pressed. You can do it like this inside of the Fragment:
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
OnBackPressedCallback callback = new OnBackPressedCallback(true) {
#Override
public void handleOnBackPressed() {
//show exit dialog
}
};
requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
}
I am trying to use realm database to display my api data. I want to display the company name, however the data is saids it is inserted in the log but cant seem to display the data on the UI. Here is the code..
Any help would be greatly appreciated with this problem. The variables are at the top and the problem is when it hits on success, ive written the code "write to DB", but it doesnt display the data but tells me the data has been inserted.
// Variables for the search input field and results TextViews.
private EditText mCompanyInput;
private TextView mTitleText;
private TextView mDescriptionText;
private TextView mOfficerText;
private TextView mTitleText1;
private TextView mDescriptionText1;
private OkHttpClient okHttpClient;
private static final String TAG = "MainActivity";
private Request request;
private String url = "https://api.companieshouse.gov.uk/search/companies?q=";
Button save;
TextView log;
Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCompanyInput = findViewById(R.id.companyInput);
log = findViewById(R.id.log);
mDescriptionText = findViewById(R.id.descriptionText);
mOfficerText = findViewById(R.id.officerText);
mTitleText1 = findViewById(R.id.titleText1);
mTitleText = findViewById(R.id.titleText);
mDescriptionText1 = findViewById(R.id.descriptionText1);
save = findViewById(R.id.searchButton);
realm = Realm.getDefaultInstance();
save.setOnClickListener(this);
}
public void onClick(View view){
okHttpClient = new OkHttpClient();
request = new Request.Builder().url(url).header("Authorization", "k6DNRbTp-AnQWn51JBz5VuPiTl8jv4_etdzoMyhf") .method("GET", null).build();
Log.d(TAG, "onClick:"+url);
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.i(TAG, e.getMessage());
}
#Override
public void onResponse(Call call, Response response) throws IOException {
Log.i(TAG,response.body().string());
Log.d(TAG, "onResponse:"+response.code());
}
});
writeToDB(mCompanyInput.getText().toString().trim(), (mDescriptionText.getText().toString().trim()));
showData();
}
public void showData(){
RealmResults<Company> guests = realm.where(Company.class).findAll();
// Use an iterator to invite all guests
String op="";
for (Company guest : guests) {
op+=guest.getName();
op+=guest.getAppointments();
}
log.setText(op);
}
public void writeToDB(final String mTitleText1, final String mDescriptionText1){
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
Company user = new Company(mTitleText1, mDescriptionText1);
bgRealm.insert(user);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
writeToDB(mCompanyInput.getText().toString().trim(), (mOfficerText.getText().toString().trim()));
showData();
// Transaction was a success.
Log.v("Database", "Data Inserted");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
Log.e("Database", error.getMessage());
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
Why are you calling writeToDB() from the onSuccess() method? This will cause recursion and keep writing the same data into the realm. It's correct to call showData() from onSuccess(), but there's not much point calling it directly from onClick().
I think your problem though is that you're trying to update the UI from a thread: it's called from an async transaction thread and not the main thread. See this answer (and there are others you can find easily once you know the problem: Updating UI / runOnUiThread / final variables: How to write lean code that does UI updating when called from another Thread.
I have a problem with my onPostExecute() method in AsyncTask class.
I have an SignupActivity:
public class SignupActivity extends AppCompatActivity implements SignupListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.signup_activity);
//new task, i pass context and interface to it
signup = new Signup(getApplicationContext(), this);
signupButon.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if(validate()) {
try {
//new task every click
Signup newSignup = new Signup(signup);
//here start AsyncTask
newSignup.execute(name, email, password).get();
} catch (Exception e) {
Toast.makeText(ERROR);
}
// if sign up succes, == true;
if(signupValid) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
}
}
});
}
// my own interface for getting result as bool from onPostExecute
#Override
public void onSignupPerformed(Boolean result){ this.signupValid = result; }
That implements my interface to catching result from onPostExecute():
public interface SignupListener{
void onSignupPerformed(Boolean result);
}
Now, AsyncTask that i trigger in code:
public class Signup extends AsyncTask<String, Boolean, Boolean> {
public Signup(Context context, SignupListener listener){
db = ApplicationDatabase.getDatabase(context);
this.context = context;
this.listener = listener;
}
public Signup(Signup signup){
//constructor to make new task based on first task
db = signup.db;
context = signup.context;
listener = signup.listener;
}
protected Boolean doInBackground(String... body){
try {
user = db.userDao().getUser(body[0], body[1], body[2]);
if (user == null) {
// user is null, so we can add new one to DB
db.userDao().insertUser(new User(body[0], body[1], body[2]));
return Boolean.TRUE; //signup go good, return true
} else {
return Boolean.FALSE; //signup go bad, return false
}
} catch(Exception e) { }
return null;
}
protected void onPostExecute(Boolean result) {
//catching result from doInBackground
listener.onSignupPerformed(result);
}
My question is, why when i first click on button, func return Boolean.TRUE but in SignupActivity signupValid variable is false (signup form not exit, but user is added to DB), but when i click signup button second time, ofc signup fail (because we make new user seconds ago) but signupValid change to true and Signup Form pass? I need to click SignupButton two times to finally exit form. Thanks for finding error in my code
EDIT:
I replaced .get() with Progress Dialog to block UI, but now i get Toast with not valid form even before AsyncTask for Signup do his job. And still, in first click signupValid is false even when from doInBackground() i get TRUE, on second click AsyncTask return FALSE but signupValid is changed to true
My UserDAO:
#Dao
public interface UserDao {
#Query("SELECT * FROM users WHERE email = :email AND password = :password AND username = :username")
User getUser(String username, String email, String password);
}
And ApplicationDatabase:
public abstract class ApplicationDatabase extends RoomDatabase {
public abstract UserDao userDao();
public static ApplicationDatabase getDatabase(final Context context){
if(INSTANCE == null){
synchronized (ApplicationDatabase.class){
if(INSTANCE == null){
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), ApplicationDatabase.class, "database").build();
}
}
}
return INSTANCE;
}
private static volatile ApplicationDatabase INSTANCE;
If I understood the problem correctly - there is a race condition that makes the SignupActivity to fire the toast before the execution of Signup task is completed. Therefore:
signupButon.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
if(validate()) {
try {
//new task every click
Signup newSignup = new Signup(signup);
//here start AsyncTask
newSignup.execute(name, email, password).get();
} catch (Exception e) {
Toast.makeText(ERROR);
}
}
}
});
While these lines:
// if sign up succes, == true;
if(signupValid) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
Should be a part of the listener (right now it seems that these lines are executed BEFORE the completion of your async task)
To clarify myself:
#Override
public void onSignupPerformed(Boolean result)
{
if(result) {
Toast.makeText(SUCCES);
finish();
} else {
Toast.makeText(ERROR);
}
}
It's the first time that I'm using this library, but I was following this video tutorial to send data through Fragments, but in my case, it's just Activities.. So this how I did
Activity that I'm sending data :
public void onClick(View view) {
String passing_data = new Gson().toJson(user);
BusStation.getBus().post(new MessageData(passing_data));
Intent intent = new Intent(activity,UserAdsView.class);
activity.startActivity(intent);
}
BusStation Class :
public class BusStation {
private static Bus bus = new Bus();
public static Bus getBus() {
return bus;
}
}
MessageData Class :
public class MessageData {
private String msgData;
public MessageData(String msgData) {
this.msgData = msgData;
}
public String getMsgData() {
return msgData;
}
}
And finally at the UserAdsView Activity :
#Override
protected void onResume() {
super.onResume();
BusStation.getBus().register(this);
}
#Override
protected void onPause() {
super.onPause();
BusStation.getBus().unregister(this);
}
#Subscribe
public void recievedData(MessageData messageData){
target = messageData.getMsgData();
Toast.makeText(getApplicationContext(), target, Toast.LENGTH_SHORT).show();
}
As was mentioned on video, this method recievedData should be fired!
When you send notification in first activity at that time, UserAdsView Activity is not registered hence there are no listeners for events.
At this line
BusStation.getBus().post(new MessageData(passing_data));
you are sending notification but there is nothing registered to receive this notification. i.e. UserAdsView Activity has not started yet.
If you need to pass data to activity at launch time, simply send it via
Intent.
add in file Gradle
dependencies {
compile 'com.squareup:otto:1.3.8'
}
Create class OttoBus
public class OttoBus {
private static Bus sBus;
public static Bus getBus() {
if (sBus == null)
sBus = new Bus();
return sBus;
}
}
Create Events Class when pass data in android
public class Events {
public static class FragmentActivityMessage {
private String message;
public FragmentActivityMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static class ActivityFragmentMessage {
private String message;
public ActivityFragmentMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
}
function pass data
public void sendMessageToFragment(View view) {
EditText etMessage = findViewById(R.id.activityData);
OttoBus.getBus().post(String.valueOf(etMessage.getText()));
}
function event getdata
#Subscribe
public void getMessage(Events.ActivityFragmentMessage message) {
TextView messageView = findViewById(R.id.message);
messageView.setText(message.getMessage());
}
You need to make your MessageData object parcelable.
Then in your onClick() :
public void onClick(View view) {
String passing_data = new Gson().toJson(user);
Bundle extras = new Bundle();
extras.putParcelable("key",new MessageData(passing_data));
Intent intent = new Intent(activity,UserAdsView.class);
intent.putExtras(extras)
activity.startActivity(intent);
}
Then in onCreate() of your UserAdsView Activity :
MessageData data = (MessageData)getIntent().getExtras().getParcelable("key");
I'm build my app with Mortar + Flow. I'm trying to figure out the correct way to show a popup that requests some text from the user. I've created this popup class:
public class SavedPageTitleInputPopup implements Popup<SavedPageTitleInput, Optional<String>> {
private final Context context;
private AlertDialog dialog;
public SavedPageTitleInputPopup(Context context) {
this.context = context;
}
#Override public Context getContext() {
return context;
}
#Override
public void show(final SavedPageTitleInput info, boolean withFlourish,
final PopupPresenter<SavedPageTitleInput, Optional<String>> presenter) {
if (dialog != null) throw new IllegalStateException("Already showing, can't show " + info);
final EditText input = new EditText(context);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
input.setLayoutParams(lp);
input.setText(info.savedPage.getName());
dialog = new AlertDialog.Builder(context).setTitle(info.title)
.setView(input)
.setMessage(info.body)
.setPositiveButton(info.confirm, new DialogInterface.OnClickListener() {
#Override public void onClick(DialogInterface d, int which) {
dialog = null;
final String newTitle = Strings.emptyToNull(String.valueOf(input.getText()));
presenter.onDismissed(Optional.fromNullable(newTitle));
}
})
.setNegativeButton(info.cancel, new DialogInterface.OnClickListener() {
#Override public void onClick(DialogInterface d, int which) {
dialog = null;
presenter.onDismissed(Optional.<String>absent());
}
})
.setCancelable(true)
.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override public void onCancel(DialogInterface d) {
dialog = null;
presenter.onDismissed(Optional.<String>absent());
}
})
.show();
}
#Override public boolean isShowing() {
return dialog != null;
}
#Override public void dismiss(boolean withFlourish) {
dialog.dismiss();
dialog = null;
}
}
This class works as expected. It uses the SavedPage to figure out what to display in the dialog and it returns the users input to the PopupPresenter using PopupPresenter#onDismissed when the correct button is pressed.
My problem is writing the PopupPresenter subclass used to present the dialog and process the input. This is what I have right now:
new PopupPresenter<SavedPage, Optional<String>>() {
#Override protected void onPopupResult(Optional<String> result) {
if (result.isPresent()) {
// The user entered something, so update the API
// Oh wait, I don't have a reference to the SavedPage
// that was displayed in the dialog!
}
}
}
As the comments say, I don't have a reference to the SavedPage that was displayed in the dialog. It was stored in the whatToShow field in PopupPresenter, but this field is nulled out right before onPopupResult is called. It seems like I would be unnecessarily repeating myself to keep an additional copy of the SavedPage.
There isn't a lot of documentation yet on PopupPresenter and Popup. The only thing I have seen is a basic example in the sample project. They create a ConfirmerPopup based on data within the Confirmation object. The purpose of the ConfirmerPopup is to capture a boolean decision from the user based on the title/body given to the Confirmation object as seen by the class declaration.
public class ConfirmerPopup implements Popup<Confirmation, Boolean> {
In your case you want to capture additional user inputted text from the user. When PopupPresenter#onPopupResult is called the result object should contain all of the data needed from SavedPageTitleInputPopup. Modify your SavedPageTitleInputPopup as follows
public class SavedPageTitleInputPopup implements Popup<SavedPage, SavedPageResults> {
private final Context context;
private AlertDialog dialog;
public SavedPageTitleInputPopup(Context context) {
this.context = context;
}
#Override public Context getContext() {
return context;
}
#Override
public void show(SavedPage info, boolean withFlourish, final PopupPresenter<SavedPage, SavedPageResults> presenter) {
if (dialog != null) throw new IllegalStateException("Already showing, can't show " + info);
// Create your Dialog but scrape all user data within OnClickListeners
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
//Anything else you need to do... .setView() or .setTitle() for example
builder.setPositiveButton(info.confirm, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface d, int which) {
dialog = null;
//Save data to SavedPageResults
final SavedPageResults results = new SavedPageResults():
presenter.onDismissed(results);
}
});
builder.setNegativeButton(info.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface d, int which) {
dialog = null;
final SavedPageResults results = new SavedPageResults();
presenter.onDismissed(results);
}
});
dialog = builder.show();
}
#Override public boolean isShowing() {
return dialog != null;
}
#Override public void dismiss(boolean withFlourish) {
dialog.dismiss();
dialog = null;
}
}
Your PopupPresenter doesn't need to know anything about the Dialog's implementation now.
new PopupPresenter<SavedPage, SavedPageResults>() {
#Override protected void onPopupResult(SavedPageResults result) {
if (result.isPresent()) {
updateUi(result.getSavedText());
}
}
}