how can i keep my edittext data when rotating my device with viewmodel? - android-fragments

Hi guys I am trying to preserve the data in an edittext by rotating the phone screen, I know I can do it with the ViewModel class, but no matter how hard I try I have not been able to.
//ViewModel
class UserViewModel: ViewModel() {
lateinit var text:MainFragment
fun data(){
text.textInputUserName.editText.toString()
}
}
//Fragment
class MainFragment : Fragment() {
private lateinit var mUserViewModel: UserViewModel
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
mUserViewModel.data()
}

You should not pass any lifecycle related components, such as Fragments, into your viewmodel, as they can cause memory leaks.
To preserve the text in an EditText upon rotation, store the EditText's text as a string in the viewmodel. You should update and use it accordingly to make sure the most recent text is preserved even after an orientation change. Example:
ViewModel
class UserViewModel: ViewModel() {
var editTextContent:String = ""
}
Fragment
class MainFragment : Fragment() {
private val mUserViewModel: UserViewModel by viewModels()// could initialize via kotlin delegated property
override fun onCreate(savedInstanceState: Bundle?){
super.onCreate(savedInstanceState)
with(mUserViewModel.editTextContent){
if(isNotEmpty()){
editText.text = text
}
else{
text = editText.text
}
editText.doAfterTextChanged{editable-> if(editable!=null) text = editable.toString()}
}
I think this codelab might be helpful for you.

Related

Navigation Drawer: how make fragments persistent (keep alive) while switching (not rotating)

With Fragment:setRetainInstance(true); the fragment is not re-instantiated on a phones orientation change.
And of course i want my fragments to be kept alive while switching from one fragment to another.
But the Android Studio 4 provides a wizard-template with only
DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
.setDrawerLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
From hours of debugging and searching the net if think it would need to inherent from the class FragmentNavigator so i can overwrite FragmentNavigator:naviagte where a new fragment gets created via final Fragment frag = instantiateFragment(.. and then is added with ft.replace(mContainerId, frag);
So i could find my old fragment and use ftNew.show and ftOld.hide instead.
Of course this is a stupid idea, because this navigate method is full of other internal stuff.
And i have no idea where that FrameNavigator is created.
I can retrieve it in the MainActivity:OnCreate with
NavigatorProvider navProvider = navController.getNavigatorProvider ();
Navigator<NavDestination> navigator = navProvider.getNavigator("fragment");
But at that time i could only replace it with my derived version. And there is no replaceNavigtor method but only a addNavigator method, which is called where ?
And anyways this all will be far to complicated and therefore error prone.
Why is there no simple option to keep my fragments alive :-(
In older Wizard-Templates there was the possibility of
#Override
public void onNavigationDrawerItemSelected(int position) {
Fragment fragment;
switch (position) {
case 1:
fragment = fragment1;
break;
case 2:
fragment = fragment2;
break;
case 3:
fragment = fragment3;
break;
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(mCurrentFragment == null) {
ft.add(R.id.container, fragment).commit();
mCurrentFragment = fragment;
} else if(fragment.isAdded()) {
ft.hide(mCurrentFragment).show(fragment).commit();
} else {
ft.hide(mCurrentFragment).add(R.id.container, fragment).commit();
}
mCurrentFragment = fragment;
}
but i have no idea how to do this with the Android 4.0 template where my MainActivity is only derived as:
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration mAppBarConfiguration;
Ideas welcome :'(
Hi there & sorry for my late answer! I had a similar problem with navigation drawers and navigation component. I tried around a little and found a working solution, which might be helpful for others too.
The key is the usage of a custom FragmentFactory in the FragmentManager of the MainActivity. See the code for this below:
public class StaticFragmentFactory extends FragmentFactory {
private myNavHostFragment1 tripNavHostFragment;
private myNavHostFragment2 settingsNavHostFragment;
#NonNull
#Override
public Fragment instantiate(#NonNull ClassLoader classLoader, #NonNull String className) {
if (MyNavHostFragment1.class.getName().equals(className)) {
if (this.myNavHostFragment1 == null) {
this.myNavHostFragment1 = new MyNavHostFragment1();
}
return this.myNavHostFragment1 ;
} else if (MyNavHostFragment2.class.getName().equals(className)) {
if (this.myNavHostFragment2 == null) {
this.myNavHostFragment2 = new MyNavHostFragment2();
}
return this.myNavHostFragment2;
}
return super.instantiate(classLoader, className);
}
}
The FragmentFactory survives the navigation between different fragments using the NavigationComponent of AndroidX. To keep the fragments alive, the FragmentFactory stores an instance of the fragments which should survive and returns this instance if this is not null. You can find a similar pattern when using a singleton pattern in classes.
You have to register the FragmentFactory in the corresponding activity by calling
this.getSupportFragmentManager().setFragmentFactory(new StaticFragmentFactory())
Please note also that I'm using nesten fragments here, so one toplevel fragment (called NavHostFragmen here) contains multiple child fragments. All fragments are using the same FragmentFactory of their parent fragments. The custom FragmentFactory above returns the result of the super class method, when the fragment to be instantiated is not known to keep alive.

How to disable button in MaterialAlertDialogBuilder

How can I disable button in MaterialAlertDialogBuilder?
I want to make similar functionality like in this screenshot:
enter image description here
I wrote the following code (dialog contains EditText where user should input his favorite food name).
final MaterialAlertDialogBuilder dialogEnterDishName = new MaterialAlertDialogBuilder(context);
//...
final EditText editTextEnterDishName = new EditText(context);
dialogEnterDishName.setView(editTextEnterDishName);
dialogEnterDishName.setPositiveButton(getString(R.string.dialog_enter_dish_name_positive_button), new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (!editTextEnterDishName.getText().toString().equals(""))
//...
else {
//TODO Make posititve button disabled until the user enters any character
}
}
});
//...
dialogEnterDishName.show();
}
I already knew, that class AlertDialog (MaterialAlertDialogBuilder extends AlertDialog.Builder) have a method public Button getButton(int whichButton), but I can't use it in MaterialAlertDialogBuilder.
Please, help!
Make sure that you are calling getButton() function after you inflate your AlertDialog (through .show() call). If you are doing it other way around there is no button to get.
In order to enable button back you can use TextWatcher. More details here: Android TextWatcher.afterTextChanged vs TextWatcher.onTextChanged
val customLayout = LayoutInflater.from(context).inflate(R.layout.your_alert_dialog, null, false)
val dialog = MaterialAlertDialogBuilder(context)
.setTitle("Provide name")
.setView(customLayout)
.setNeutralButton("Cancel") { dialog, _ -> dialog.dismiss() }
.setPositiveButton("Confirm") { _, _ -> }
.create()
dialog.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false

Communication between ParentFragment and Fragment in ViewPager

I have a MainActivity which holds HomeFragment
HomeFragment has sort button Viewpager which holds 2 fragments
when sort button is clicked in HomeFragment, the value has to be passed to ViewPager Fragmensts
I google where I found communication between Fragments which is placed directly in MainActivity
I tried to solve this by myself with the help of stackoverflow
HomeFragment.kt
private var sort: Int = 0
private var sortListener: SortListener? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val childFragment = adapter?.instantiateItem(viewPager, fragmentPosition) as ChildFragment
setSortListener(childFragment)
}
fun userClickinSort(){
sortListener?.onSorted(HomeConstants.SORT_TYPE_NAME)
}
private fun setSortListener(sortListener: SortListener) {
this.sortListener = sortListener
}
Note: implemented this SortListener in ChildFragment

Structuring a MonoTouch.Dialog application

From the examples at Xamarin.com you can build basic M.T. Dialog apps, but how do you build a real life application?
Do you:
1) Create a single DialogViewController and tree every view/RootElement from there or,
2) Create a DialogViewController for every view and use the UINavigationController and push it on as needed?
Depending on your answer, the better response is how? I've built the example task app, so I understand adding elements to a table, click it to go to the 'next' view for editing, but how to click for non-editing? How to click a button, go next view if answer is number 1?
Revised:
There is probably no one right answer, but what I've come up with seems to work for us. Number 2 from above is what was chosen, below is an example of the code as it currently exists. What we did was create a navigation controller in AppDelegate and give access to it throughout the whole application like this:
public partial class AppDelegate : UIApplicationDelegate
{
public UIWindow window { get; private set; }
//< There's a Window property/field which we chose not to bother with
public static AppDelegate Current { get; private set; }
public UINavigationController NavController { get; private set; }
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
Current = this;
window = new UIWindow (UIScreen.MainScreen.Bounds);
NavController = new UINavigationController();
// See About Controller below
DialogViewController about = new AboutController();
NavController.PushViewController(about, true);
window.RootViewController = NavController;
window.MakeKeyAndVisible ();
return true;
}
}
Then every Dialog has a structure like this:
public class AboutController : DialogViewController
{
public delegate void D(AboutController dvc);
public event D ViewLoaded = delegate { };
static About about;
public AboutController()
: base(about = new About())
{
Autorotate = true;
about.SetDialogViewController(this);
}
public override void LoadView()
{
base.LoadView();
ViewLoaded(this);
}
}
public class About : RootElement
{
static AboutModel about = AboutVM.About;
public About()
: base(about.Title)
{
string[] message = about.Text.Split(...);
Add(new Section(){
new AboutMessage(message[0]),
new About_Image(about),
new AboutMessage(message[1]),
});
}
internal void SetDialogViewController(AboutController dvc)
{
var next = new UIBarButtonItem(UIBarButtonSystemItem.Play);
dvc.NavigationItem.RightBarButtonItem = next;
dvc.ViewLoaded += new AboutController.D(dvc_ViewLoaded);
next.Clicked += new System.EventHandler(next_Clicked);
}
void next_Clicked(object sender, System.EventArgs e)
{
// Load next controller
AppDelegate.Current.NavController.PushViewController(new IssuesController(), true);
}
void dvc_ViewLoaded(AboutController dvc)
{
// Swipe location: https://gist.github.com/2884348
dvc.View.Swipe(UISwipeGestureRecognizerDirection.Left).Event +=
delegate { next_Clicked(null, null); };
}
}
Create a sub-class of elements as needed:
public class About_Image : Element, IElementSizing
{
static NSString skey = new NSString("About_Image");
AboutModel about;
UIImage image;
public About_Image(AboutModel about)
: base(string.Empty)
{
this.about = about;
FileInfo imageFile = App.LibraryFile(about.Image ?? "filler.png");
if (imageFile.Exists)
{
float size = 240;
image = UIImage.FromFile(imageFile.FullName);
var resizer = new ImageResizer(image);
resizer.Resize(size, size);
image = resizer.ModifiedImage;
}
}
public override UITableViewCell GetCell(UITableView tv)
{
var cell = tv.DequeueReusableCell(skey);
if (cell == null)
{
cell = new UITableViewCell(UITableViewCellStyle.Default, skey)
{
SelectionStyle = UITableViewCellSelectionStyle.None,
Accessory = UITableViewCellAccessory.None,
};
}
if (null != image)
{
cell.ImageView.ContentMode = UIViewContentMode.Center;
cell.ImageView.Image = image;
}
return cell;
}
public float GetHeight(UITableView tableView, NSIndexPath indexPath)
{
float height = 100;
if (null != image)
height = image.Size.Height;
return height;
}
public override void Selected(DialogViewController dvc, UITableView tableView, NSIndexPath indexPath)
{
//base.Selected(dvc, tableView, path);
tableView.DeselectRow(indexPath, true);
}
}
#miquel
The current idea of a workflow is an app that starts with a jpg of the Default.png that fades into the first view, with a flow control button(s) that would move to the main app. This view, which I had working previous to M.T.D. (MonoTouch.Dialog), which is a table of text rows with an image. When each row is clicked, it moves to another view that has the row/text in more detail.
The app also supports in-app-purchasing, so if the client wishes to purchase more of the product, then switch to another view to transact the purchase(s). This part was the main reason for switching to M.T.D., as I thought M.T.D. would be perfect for it.
Lastly there would be a settings view to re-enable purchases, etc.
PS How does one know when the app is un-minimized? We would like to show the fade in image again.
I have been asking myself the same questions. I've used the Funq Dependency Injection framework and I create a new DialogViewController for each view. It's effectively the same approach I've used previously developing ASP.NET MVC applications and means I can keep the controller logic nicely separated. I subclass DialogViewController for each view which allows me to pass in to the controller any application data required for that particular controller. I'm not sure if this is the recommended approach but so far it's working for me.
I too have looked at the TweetStation application and I find it a useful reference but the associated documentation specifically says that it isn't trying to be an example of how to structure a MonoTouch application.
I use option 2 that you stated as well, it works pretty nicely as you're able to edit the toolbar options on a per-root-view basis and such.
Option 2 is more feasible, as it also gives you more control on each DialogViewController. It can also helps if you want to conditionally load the view.

Defining a custom UINavigationBar through subclassing removes navigation items

I'm trying to follow the standard approach to creating a custom UINavigationBar in order to change its background image, but have found an issue in the subclassing process. If I subclass UINavigationController, with the intent of overriding the virtual NavigationBar property to provide my own implementation, all navigation items (any left or right buttons, and the title view) disappear. At first I thought it was due to the background being rendered over top of the navigation items, but I can reproduce the problem with a no-op subclass.
It's reproducible with the following code:
[Register("NavigationBar")]
public class NavigationBar : UINavigationBar
{
public NavigationBar () : base()
{
}
public NavigationBar (NSCoder coder) : base(coder)
{
}
public NavigationBar (IntPtr ptr) : base(ptr)
{
}
public NavigationBar (NSObjectFlag t) : base(t)
{
}
public NavigationBar (RectangleF frame) : base(frame)
{
}
}
[Register("NavigationController")]
public class NavigationController : UINavigationController
{
private UINavigationBar _navBar;
public NavigationController () : base()
{
}
public NavigationController (NSCoder coder) : base(coder)
{
}
public NavigationController (IntPtr ptr) : base(ptr)
{
}
public NavigationController (NSObjectFlag t) : base(t)
{
}
public override UINavigationBar NavigationBar
{
get
{
if(_navBar == null)
{
return base.NavigationBar;
}
return _navBar;
}
}
public void SetNavigationBar(UINavigationBar navigationBar)
{
_navBar = (UINavigationBar)navigationBar;
}
}
Now, all you need to do to lose your navigation items is to use the custom classes instead of the default ones:
var navigationBar = new NavigationBar();
navigationBar.BarStyle = UIBarStyle.Black;
navigationBar.TintColor = HeaderColor;
var navigationController = new NavigationController();
navigationController.SetNavigationBar(navigationBar);
// ...
Well, your SetNavigationBar() method doesn't pass that down to the native base class and since you don't do any explicit drawing yourself, how is the native drawing code ever supposed to be invoked for your custom NavigationBar class?
In your example code, that NavigationBar is just floating around in space and never gets told to draw.
In order to subclass UINavigationBar, you must define the IntPtr constructor in your derived class and instantiate the UINavigationController using the public UINavigationController(Type navigationBarType, Type toolbarType) constructor. Example:
public class MyNavigationBar: UINavigationBar
{
public MyNavigationBar(IntPtr h) : base(h)
{
}
// Do something.
}
....
var navController = new UINavigationController(typeof(MyNavigationBar), typeof(UIToolbar));
Took me a while to figure it out. More information on this page: http://developer.xamarin.com/guides/ios/platform_features/introduction_to_ios_6/ in section Subclassing UINavigationBar.
Can you create a sample project where you're adding NavigationItems directly to a UINavigationController and then using the sub-classed UINavigationController/UINavigationBar causes these buttons to disappear?
Thanks,
ChrisNTR
After a lot of research and back and forth with Xamarin, the answer to this problem is that you must use an IB stub file that is essentially no-op, but exists to shuttle the desired base type for your navigation elements. There is a working example on my OSS project: http://github.com/danielcrenna/artapp

Resources