Working with the android studio Bottom Navigation activity template, want my HomeFragment / HomeViewModel page text to change each time the Home is accessed from the bottom navigation. When I click it the first time the text it displays should be different from the text displayed the 2nd time.
I'm able to create a variable that holds the text output, and I've created a function that should be able to update that variable but I'm unable to call it up within the MainActivity class. Any ideas?
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications))
navView.setupWithNavController(navController)
}
HomeFragment
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
var chosenOutput:String = "primary output";
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//using update here crashes the app
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
homeViewModel.text.observe(viewLifecycleOwner, Observer {
textView.text = chosenOutput; //changing 'chosenOutput' to 'it' means that text update must take place within HomeViewModel instead of fragment.
})
return root
}
fun update(result: String)
{
chosenOutput = result;
}
}
HomeViewModel
class HomeViewModel : ViewModel() {
var temp:String = "demo"
//constructor()
private val _text = MutableLiveData<String>().apply {
value = temp;
}
fun update(result: String) //function may be obsolete if text change can be done in fragment.
{
temp = result;
}
val text: LiveData<String> = _text
}
What is the correct way to inject a string into either the Fragment or the ViewModel from the MainActivity class I've given?
You could change your approach and store your chosenOutput variable in the MainActivity
and update it everytime you select the HomeFragment
class MainActivity : AppCompatActivity()
{
var chosenOutput = "primary output"
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navView: BottomNavigationView = findViewById(R.id.nav_view)
val navController = findNavController(R.id.nav_host_fragment)
val appBarConfiguration = AppBarConfiguration(setOf(
R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications))
navView.apply {
setupWithNavController(navController)
setOnNavigationItemSelectedListener { menuItem ->
when (menuItem.itemId) {
R.id.navigation_home -> {
chosenOutput = "new value" // <-- Update chosen output here
// Add this method to call the `onResume` method in the fragment
showFragment(HomeFragment.newInstance())
}
else { }
}
true
}
}
private fun showFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.nav_host_fragment, fragment)
.addToBackStack(null)
.commit()
}
}
Then you can retrieve the updated string in your Fragment with this code.
Note that I added the companion object to avoid the creation of multiple instances of the Fragment.
class HomeFragment : Fragment()
{
companion object
{
fun newInstance() : HomeFragment
{
return HomeFragment()
}
}
override fun onResume() {
super.onResume()
val newChosenOutput = (activity as MainActivity).chosenOutput
}
}
Related
I am making an editor tool that allows me to add parts to instances of scriptable-object class I've made. I use a generic method to add the new parts:
[CreateAssetMenu]
class Whole : ScriptableObject {
List<PartBase> parts = new();
public void AddPart<T>() where T:PartBase, new() { parts.Add(new T()); }
}
class Foo {
//insert selection statement that goes through each Child class of PartBase
}
The way I'm currently doing what I intend to do looks like this:
switch (EPartType)
{
case EPartType.Bar:
AddPart<BarPart>();
case EPartType.Baz:
AddPart<BazPart>();
case EPartType.Bor:
AddPart<BorPart>();
default:
break;
}
I am wondering if there's a way to do this that doesn't require me to switch on every single sub-class of PartBase (so that users can add their own custom parts just by making a new script inheriting from PartBase and not having to additionally tinker with my safekept enum), while still providing some sort of enumerated selection that can be used as a drop-down in my tool.
Thank you in advance!
I know this goes a bit beyond the scope of your question but here you go.
The comments in the code should hopefully explain every step
[CreateAssetMenu]
public class Whole : ScriptableObject
{
public List<PartBase> parts = new();
#if UNITY_EDITOR
// A custom Inspector for this type to extend it with an additional button
[CustomEditor(typeof(Whole))]
private class WholeEditor : Editor
{
// serialized property for the "parts"
private SerializedProperty m_PartsProperty;
// tiny hack to simulate a dropdown (see below)
private Rect _rect;
private void OnEnable()
{
// link up serialized property
m_PartsProperty = serializedObject.FindProperty(nameof(parts));
}
public override void OnInspectorGUI()
{
// draw the default inspector
base.OnInspectorGUI();
EditorGUILayot.Space();
var buttonClicked = GUILayout.Button("Add Part");
if (Event.current.type == EventType.Repaint)
{
// little hack to get the button rect in order to place
// our popup window here to kinda simulate a dropdown
_rect = GUIUtility.GUIToScreenRect(GUILayoutUtility.GetLastRect());
}
if (buttonClicked)
{
// Get the FOLDER where the current main asset is placed
// we will place created part assets here as well
var mainAssetPath = AssetDatabase.GetAssetPath(target);
var parts = mainAssetPath.Split('/').ToList();
parts.RemoveAt(parts.Count - 1);
mainAssetPath = string.Join('/', parts);
// shift the target position lower to start the window right under the "Add Part" button
_rect.y += _rect.height;
WholeEditorAddPopup.OpenPopup(_rect, m_PartsProperty, mainAssetPath);
}
}
private class WholeEditorAddPopup : EditorWindow
{
// The property where to finally add the created part
private SerializedProperty m_ListProperty;
// available Types and their according display names
private Type[] m_AvailableTypes;
private GUIContent[] m_DisplayOptions;
// just the label for the dropdown
private readonly GUIContent m_Label = new("Part to add");
// Folder where to create assets
private string m_MainAssetPath;
// currently selected type index
private int m_Selected = -1;
public static void OpenPopup(Rect buttonRect, SerializedProperty listProperty, string mainAssetPath)
{
// create a new instance of this window
var window = GetWindow<WholeEditorAddPopup>(true, "Add Part");
// assign the fields
window.m_ListProperty = listProperty;
window.m_MainAssetPath = mainAssetPath;
// get all assemblies
window.m_AvailableTypes = AppDomain.CurrentDomain.GetAssemblies()
// get all Types
.SelectMany(assembly => assembly.GetTypes())
// Filter to only have non-abstract child classes of "PartBase"
.Where(type => type.IsSubclassOf(typeof(PartBase)) && !type.IsAbstract)
// order by "FullName" (=> including namespaces)
.OrderBy(type => type.FullName)
.ToArray();
// For the display names replace all "." by "/"
// => Unity treats those as nested folders in the popup (see demo below)
window.m_DisplayOptions = window.m_AvailableTypes.Select(type => new GUIContent(type.FullName.Replace('.', '/'))).ToArray();
// show as Dropdown -> clicking outside automatically closes window
window.ShowAsDropDown(buttonRect, new Vector2(buttonRect.width, EditorGUIUtility.singleLineHeight * 4));
// [optional] set position again since "ShowAsDropDown" might have hanged it
window.position = new Rect(buttonRect.x, buttonRect.y, buttonRect.width, EditorGUIUtility.singleLineHeight * 4);
}
private void OnGUI()
{
// Draw a dropdown button containing all the available types
// grouped by namespaces and ordered alphabetically
m_Selected = EditorGUILayout.Popup(m_Label, m_Selected, m_DisplayOptions);
// only enable the "Add" button if valid index selected
var blockAdd = m_Selected < 0;
EditorGUILayout.Space();
using (new EditorGUI.DisabledScope(blockAdd))
{
if (GUILayout.Button("Add"))
{
// get selected type by selected index
var selectedType = m_AvailableTypes[m_Selected];
// create runtime ScriptableObject instance by selected type
var part = CreateInstance(selectedType);
// Set its initial name
part.name = $"new {selectedType.Name}";
// Get a unique path for this asset
// => if already an asset with same name Unity adds an auto-incremented index
var path = AssetDatabase.GenerateUniqueAssetPath($"{m_MainAssetPath}/{part.name}.asset");
// Create the asset, save and refresh
AssetDatabase.CreateAsset(part, path);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
// not sure anymore but from an old experience I think you need to re-load the asset
var loadedPart = AssetDatabase.LoadAssetAtPath<PartBase>(path);
// add the loaded asset to the "parts" list
m_ListProperty.arraySize += 1;
var elementProperty = m_ListProperty.GetArrayElementAtIndex(m_ListProperty.arraySize - 1);
elementProperty.objectReferenceValue = loadedPart;
// finally make the modified SerializedProperties persistent in the actual "Whole" asset
m_ListProperty.serializedObject.ApplyModifiedProperties();
// [optional] "Ping" the created asset => get highlighted in the Assets folder
EditorGUIUtility.PingObject(loadedPart);
// Close the popup window
Close();
}
}
}
}
}
#endif
}
And here a little demo how this would look like
For the demo I created the following types - all in their individual script files of course
public class PartBase : ScriptableObject { }
public class ExamplePart : PartBase { }
namespace NamespaceA
{
public class PartA : PartBase { }
}
namespace NamespaceA
{
public class PartAExtended : PartA { }
}
namespace NamespaceB
{
public class PartB : PartBase { }
}
namespace NamespaceB
{
public class PartBExtended : PartB { }
}
I have a viewPager with 3 tab fragments.
The code of tabFragment1 class (Note: V4Fragment = Android.Support.V4.App.Fragment):
class tabFragment1 : V4Fragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved)
{
var v = inflater.Inflate(Resource.Layout.tabLayout1, container, false);
// Set weather icon
ImageView iv_icon = v.FindViewById<ImageView>(Resource.Id.iv_icon);
iv_icon.SetImageResource(Resource.Drawable.ic_weather_cloudy_white_48dp);
Snackbar.Make(container, "tabFragment; onCreateView()", Snackbar.LengthLong).Show();
//UpdateContent(v);
return v;
}
public void UpdateContent(View v)
{
// Sample values to read
string locationCode = "528454";
// Mapping views
TextView tv_currentConditions = v.FindViewById<TextView>(Resource.Id.currentConditions);
TextView tv_currentTemperature = v.FindViewById<TextView>(Resource.Id.currentTemperature);
TextView tv_currentRealFeel = v.FindViewById<TextView>(Resource.Id.currentRealFeel);
TextView tv_minMaxTemperature = v.FindViewById<TextView>(Resource.Id.minMaxTemperature);
// Update view from database
tv_currentConditions.Text = Sql.ReadValue(locationCode,"currentConditions");
tv_currentTemperature.Text = Sql.ReadValue(locationCode, "currentTemperature") + "°C";
tv_currentRealFeel.Text = GetString(Resource.String.feelLike) + " " + Sql.ReadValue(locationCode, "currentRealFeel") + "°C";
tv_minMaxTemperature.Text = Sql.ReadValue(locationCode, "minTemperature") + " " + Sql.ReadValue(locationCode, "maxTemperature") + "°C";
}
}
So, I want to call UpdateContent() method from MainActivity method:
public void UpdateCurrentTab()
{
ViewPager viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
int currentItem = viewPager.CurrentItem;
Snackbar.Make(drawerLayout, "CALL TO UPDATE Tab " + currentItem, Snackbar.LengthLong).Show();
View fView = viewPager.GetChildAt(currentItem);
if (currentItem == 1) { tabFragment1.UpdateContent(fView); }
}
It works fine (cause I see I get correct itemIDs in Snackbar appearing every time I select a tab), but on the last row under if clause ...
if (currentItem == 1) { tabFragment1.UpdateContent(fView); }
... i see Visual Studio error pop-up saying, that
an object reference is required for the nonstatic field, method, or property 'tabFragment1.UpdateContent(fView)'
What is the best way to silve the problem? Thanks
public void UpdateContent(View v) is object's method not type's method. So for using it you need to create object of tabFragment1 and call it method. Or you can change declaration for UpdateContent(View v) to static UpdateContent(View v) and call it as type's method.
Changing declaration:
public static void UpdateContent(View v)
{
// Put here your code from question
}
And using:
public void UpdateCurrentTab()
{
ViewPager viewPager = FindViewById<ViewPager>(Resource.Id.viewpager);
int currentItem = viewPager.CurrentItem;
Snackbar.Make(drawerLayout, "CALL TO UPDATE Tab " + currentItem, Snackbar.LengthLong).Show();
View fView = viewPager.GetChildAt(currentItem);
if (currentItem == 1)
{
// Now it's a type's method not object's
tabFragment1.UpdateContent(fView);
}
}
I am trying to develop a simple custom web control for ASP.Net WebForms that has a collections property called Subscriptions.
I can compile the control project successfully and add it from toolbox to an aspx page without any issues.
The problem is when I add entries for Subscriptions property using the collections editor in design view in Visual Studio 2013.
I can input multiple Subscriptions but when I click on OK button of the collections editor and then I go back to Subscriptions property in design view it's empty even though I had input some entries a moment ago.
Markup of custom control in aspx
<cc1:WebControl1 ID="WebControl1" runat="server"></cc1:WebControl1>
Question : What is not correct with my code that is causing the collections to not show up in control's markup in design view?
Custom web control code
namespace WebControl1
{
[ToolboxData("<{0}:WebControl1 runat=\"server\"> </{0}:WebControl1>")]
[ParseChildren(true)]
[PersistChildren(false)]
public class WebControl1 : WebControl
{
[Bindable(true)]
[Category("Appearance")]
[DefaultValue("")]
[Localizable(true)]
public string Text
{
get
{
String s = (String)ViewState["Text"];
return ((s == null) ? "[" + this.ID + "]" : s);
}
set
{
ViewState["Text"] = value;
}
}
[
Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> Subscriptions { get; set; }
protected override void RenderContents(HtmlTextWriter output)
{
output.Write(Text);
}
}
}
[TypeConverter(typeof(ExpandableObjectConverter))]
public class Subscription
{
private string name;
private decimal amount;
public Subscription()
: this(String.Empty, 0.00m)
{
}
public Subscription(string nm, decimal amt)
{
name = nm;
amount = amt;
}
[
Category("Behavior"),
DefaultValue(""),
Description("Name of subscription"),
NotifyParentProperty(true),
]
public String Name
{
get
{
return name;
}
set
{
name = value;
}
}
[
Category("Behavior"),
DefaultValue("0.00"),
Description("Amount for subscription"),
NotifyParentProperty(true)
]
public decimal Amount
{
get
{
return amount;
}
set
{
amount = value;
}
}
}
public class SubscriptionCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
public SubscriptionCollectionEditor(Type type)
: base(type)
{
}
protected override bool CanSelectMultipleInstances()
{
return false;
}
protected override Type CreateCollectionItemType()
{
return typeof(Subscription);
}
}
I was able to solve the problem by making following 2 changes.
Since for collections like List the .Net framework will automatically display an appropriate editor so we don't need to specify the editor since the collection is of List type. So we don't need this attribute Editor(typeof(SubscriptionCollectionEditor), typeof(UITypeEditor)).
The setter for Subscriptions needs to be removed and only a get should be there as in code below. If a setter is used then it should be used as in second code snippet. But automatic get and set should not be used with collection property in a custom web control.
The final code for the collections property should look like below and then collections will not disappear when one returns to it later on in design-time VS 2013.
Code that works without a setter
private List<Subscription> list = null;
[Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> SubscriptionList
{
get
{
if (lists == null)
{
lists = new List<Subscription>();
}
return lists;
}
}
Code that works with a setter
[Category("Behavior"),
Description("The subscriptions collection"),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible),
PersistenceMode(PersistenceMode.InnerDefaultProperty)
]
public List<Subscription> SubscriptionList
{
get
{
object s = ViewState["SubscriptionList"];
if ( s == null)
{
ViewState["SubscriptionList"] = new List<Subscription>();
}
return (List<Subscription>) ViewState["SubscriptionList"];
}
set
{
ViewState["SubscriptionList"] = value;
}
}
I want add search logic for my application (IOS8). I have simple MvxTableViewController and display my data by UITableViewSource. Here is:
...controller:
MvxViewFor(typeof(MainViewModel))]
partial class MainController : MvxTableViewController
{
public MainController(IntPtr handle) : base(handle) { }
public override void ViewDidLoad()
{
base.ViewDidLoad();
// make background trasnsparent page
this.View.BackgroundColor = UIColor.Clear;
this.TableView.BackgroundColor = UIColor.Clear;
this.NavigationController.NavigationBar.BarStyle = UIBarStyle.Black;
this.SetBackground ();
(this.DataContext as MainViewModel).PropertyChanged += this.ViewModelPropertyChanged;
}
private void SetBackground()
{
// set blured bg image
}
private void ViewModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
var viewModel = this.ViewModel as MainViewModel;
if (e.PropertyName == "Title")
{
this.Title = viewModel.Title;
}
else if (e.PropertyName == "Topics")
{
var tableSource = new TopicTableViewSource(viewModel.Topics);
tableSource.TappedCommand = viewModel.NavigateToChildrenPageCommand;
this.TableView.Source = tableSource;
this.TableView.ReloadData();
}
}
I read about search in IOS and choosed UISearchController for IOS8 app. But I don't understand, how I can add this controller to my view :(
I found sample from Xamarin (TableSearch) - but they don't use UITableViewSource and I don't understand what I should do with this.
I tried add controller:
this.searchController = new UISearchController (this.searchTableController)
{
WeakDelegate = this,
DimsBackgroundDuringPresentation = false,
WeakSearchResultsUpdater = this,
};
this.searchController.SearchBar.SizeToFit ();
this.TableView.TableHeaderView = searchController.SearchBar;
this.TableView.WeakDelegate = this;
this.searchController.SearchBar.WeakDelegate = this;
what should I do in this.searchTableController? Do I need move my display logic there?
Yes. The "searchTableController" should be responsible for the presentation of search results.
Here is the test project (native, not xmarin) which help you understand.
The searchController manages a "searchBar" and "searchResultPresenter". His not need add to a view-hierarchy of the carrier controller. When user starts typing a text in the "searchBar" the "SearchController" automatically shows your SearchResultPresenter for you.
Steps:
1) Instantiate search controller with the SearchResultsPresenterController.
2) When user inputs text in the search-bar you should invoke a your own service for the search. Below a sample of code..
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
{
NSString *searchString = searchController.searchBar.text;
if (searchString.length > 1)
{
// TODO - call your service for the search by string
// this may be async or sync
// When a data was found - set it to presenter
[self.searchResultPresenter dataFound:<found data>];
}
}
3) In the search presenter need to reload a table in the method "dataFound:"
- (void)dataFound:(NSArray *)searchResults
{
_searchResults = searchResults;
[self.tableView reloadData];
}
Here are some advice on how to use the UISearchController with Xamarin.iOS.
Create a new class for the results table view subclassing UITableViewSource. This is gonna be the view responsible of displaying the results. You need to make the items list of that table view public.
public List<string> SearchedItems { get; set; }
In your main UIViewController, create your UISearchController and pass your result table view as an argument. I added some extra setup.
public UISearchController SearchController { get; set; }
public override void ViewDidLoad ()
{
SearchController = new UISearchController (resultsTableController) {
WeakDelegate = this,
DimsBackgroundDuringPresentation = false,
WeakSearchResultsUpdater = this,
};
SearchController.SearchBar.SizeToFit ();
SearchController.SearchBar.WeakDelegate = this;
SearchController.HidesNavigationBarDuringPresentation = false;
DefinesPresentationContext = true;
}
The best way to add the search bar to your UI in term of user experience, in my opinion, is to add it as a NavigationItem to a NavigationBarController.
NavigationItem.TitleView = SearchController.SearchBar;
Add methods to perform the search in the main UIViewController:
[Export ("updateSearchResultsForSearchController:")]
public virtual void UpdateSearchResultsForSearchController (UISearchController searchController)
{
var tableController = (UITableViewController)searchController.SearchResultsController;
var resultsSource = (ResultsTableSource)tableController.TableView.Source;
resultsSource.SearchedItems = PerformSearch (searchController.SearchBar.Text);
tableController.TableView.ReloadData ();
}
static List<string> PerformSearch (string searchString)
{
// Return a list of elements that correspond to the search or
// parse an existing list.
}
I really hope this will help you, good luck.
I have created a sample on github Github link
When clicking on a row or trying to scroll in the UIPickerView it will crash and i wonder why.
// create a ActionSheet
var actionPickerSheet = new UIActionSheet("Select a Category");
actionPickerSheet.Style = UIActionSheetStyle.BlackTranslucent;
// Create UIpickerView
var catPicker = new UIPickerView(){
Model = new catPickerModel(),
AutosizesSubviews = true,
Hidden = false,
ShowSelectionIndicator = true
};
// show inside view and add the catPicker as subView
actionPickerSheet.ShowInView(View);
actionPickerSheet.AddSubview(catPicker);
// resize both views so it fits smoothly
actionPickerSheet.Frame = new RectangleF(0,100,320,500);
catPicker.Frame = new RectangleF(actionPickerSheet.Frame.X,actionPickerSheet.Frame.Y-25,actionPickerSheet.Frame.Width, 216);
And the Model
private class catPickerModel : UIPickerViewModel
{
public string[] protocolNames = new string[]
{
"Web", "Phone Call", "Google Maps", "SMS", "Email"
};
public override int GetComponentCount(UIPickerView uipv)
{
return 1;
}
public override int GetRowsInComponent( UIPickerView uipv, int comp)
{
//each component has its own count.
int rows = protocolNames.Length;
return(rows);
}
public override string GetTitle(UIPickerView uipv, int row, int comp)
{
//each component would get its own title.
return protocolNames[row];
}
public override void Selected(UIPickerView uipv, int row, int comp)
{
Console.WriteLine("selected:" + row);
}
public override float GetComponentWidth(UIPickerView uipv, int comp)
{
return 300f;
}
}
I have no idea why it keeps crashing, is it some method i am missing in the Model or am i trying to do this in the wrong way ?
Thanks
Try to make your catPicker to a private variable of the class:
namespace TextfieldUIPickerView
{
public partial class TextfieldUIPickerViewViewController : UIViewController
{
private UIPickerView catPicker;
...
Looks like the GC has collected your catPicker and disposed it.