How to pass array of View/Component in react native? - android

I am making App framework in React Native. Framework consist of some basic screens like login screen, Tab screen. The purpose of the framework is to provide fundamental design to new app which will be developed from grounds up.
Tab Screen
As we know each Tab will have individual view to display. I want to make tabscreen totally customizable. It means based on passed View list and tab list, it should render.
For that i need to pass Array of View/Component as a prop to TabScreen.
How can I make array of View/Component?
How to pass array as props to TabScreen?
below is the code of TabScreen
'uses strict'
import {StyleSheet, View, Text, Image} from 'react-native';
import React, {Component, PropTypes} from 'react';
import {IndicatorViewPager, PagerTabIndicator} from 'rn-viewpager';
export default class TabScreen extends Component {
constructor (props) {
super(props)
}
static propTypes = {
views : PropTypes.arrayOf(PropTypes.instanceOf(View)).isRequired,
tabs: PropTypes.arrayOf(PropTypes.shape({
text: PropTypes.string,
iconSource: Image.propTypes.source,
selectedIconSource: Image.propTypes.source
})).isRequired,
}
render() {
return (
<View style={{flex:1}}>
<IndicatorViewPager
style={{flex:1, paddingTop:20, backgroundColor:'white'}}
indicator={<PagerTabIndicator tabs={this.props.tabs} />}>
{views}
</IndicatorViewPager>
</View>
);
}
}
module.exports = TabScreen;
Please help

You don't need to pass an array of react native components, you have to use the children of the component, like this:
In the render method of your upper-level component:
<TabScreen>
<View><Text>First tab</Text>
<View><Text>Second tab</Text></View>
{/* And so on... */}
</TabScreen>
Then, in your TabScreen component, just do:
render() {
return (
<View style={{flex:1}}>
<IndicatorViewPager
style={{flex:1, paddingTop:20, backgroundColor:'white'}}
indicator={<PagerTabIndicator tabs={this.props.tabs} />}>
{this.props.children}
</IndicatorViewPager>
</View>
);
}
In any case, in response to your questions:
How can I make array of View/Component?
Just as any other array. For instance, with a map:
let elements = ['First Tab', 'Second Tab'].map((text)=> <View><Text>{text}</Text></View>))
How to pass array as props to TabScreen?
Arrays are no different to any other data type when it comes to props (any variable can be passed as a prop, unless it has some sort of validation mechanism, in which case it will raise a warning).

Related

Is there an equivalent of html datalist in Xamarin.Forms, mechanism that would allow to select predefined values but also a free-text entry?

We are building a Xamarin forms app.
One of the fields is supposed to be a select where you can pick one of the predefined values or enter a free text value like into the text field.
in HTML one would solve it by using <datalist>
<input list="browsers" name="browser" id="browser">
<datalist id="browsers">
<option value="Edge">
<option value="Firefox">
<option value="Chrome">
<option value="Opera">
<option value="Safari">
</datalist>
Is there an equivalent of HTML datalist control in Xamarin.Forms, that would allow selection of values and also a free-text entry?
If there isn't one, how is this sort of functionality (selection of values and also a free-text) achieved in iOS and Android? as it does feel like a quite common scenario.
Answer
I recommend using the Syncfusion.Xamarin.SfAutoComplete NuGet Package.
Note: Syncfusion can be used for Free via its Community License.
I've put together a sample app demonstrating how to use Syncfusion.Xamarin.SfAutoComplete: https://github.com/brminnick/AutoCompleteSample
Walkthrough
To view the completed code from this walkthrough, visit https://github.com/brminnick/AutoCompleteSample
1. Install Syncfusion.Xamarin.SfAutoComplete NuGet Package
In Visual Studio, add the Syncfusion.Xamarin.SfAutoComplete NuGet Package to your iOS project, Android project and .NET Standard Project (if applicable).
2. Initialize Syncfusion.Xamarin.SfAutoComplete on iOS
In the iOS Project, open AppDelegate.cs
In the AppDelegate.cs file, in AppDelegate.FinishedLaunching method, add new Syncfusion.SfAutoComplete.XForms.iOS.SfAutoCompleteRenderer();, like so:
[Register(nameof(AppDelegate))]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
new Syncfusion.SfAutoComplete.XForms.iOS.SfAutoCompleteRenderer();
global::Xamarin.Forms.Forms.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
3. Use Syncfusion.Xamarin.SfAutoComplete in Xamarin.Forms
Here is an example of a Xamarin.Forms app using Syncfusion.Xamarin.SfAutoComplete:
using System.Collections.Generic;
using Syncfusion.SfAutoComplete.XForms;
using Xamarin.Forms;
namespace AutoCompleteSample
{
public class App : Application
{
public App() => MainPage = new AutoCompletePage();
}
class AutoCompletePage : ContentPage
{
public AutoCompletePage()
{
Content = new SfAutoComplete
{
HeightRequest = 40,
AutoCompleteSource = new List<string>
{
"Edge",
"Firefox",
"Chrome",
"Opera",
"Safari",
}
};
}
}
}
Demo
This GIF was generated using this completed sample app: https://github.com/brminnick/AutoCompleteSample
Is there an equivalent of HTML datalist control in Xamarin.Forms, that would allow selection of values and also a free-text entry?
Xamarin.Forms don't have the same control, but you can create a custom control to have these features.
A custom control that contains Listview, Entry, and Stacklayout.
nuget XLabs.Forms
Install-Package XLabs.Forms -Version 2.0.5782
https://github.com/XLabs/Xamarin-Forms-Labs/blob/master/src/Forms/XLabs.Forms/Controls/AutoCompleteView.cs
Then using this custom control AutoCompleteView.
<ContentPage.Resources>
<!-- this isn't working yet -->
<DataTemplate x:Key="SugestionItemTemplate">
<ViewCell Height="60">
<ViewCell.View>
<StackLayout HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
<Label Text="{Binding .}" VerticalOptions="Center" />
</StackLayout>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout>
<local:AutoCompleteView
Placeholder="type 'firstname'"
SearchCommand="{Binding SearchCommand}"
SelectedItem="{Binding SelectedItem}"
ShowSearchButton="False"
SuggestionItemDataTemplate="{StaticResource SugestionItemTemplate}"
Suggestions="{Binding Items, Mode=TwoWay}" />
</StackLayout>
</ContentPage.Content>
Backing class
public class listviewmodel:ViewModelBase
{
public ObservableCollection<string> Items { get; set; }
private Command<string> _searchCommand;
private string _selecteditem;
public listviewmodel()
{
Items = new ObservableCollection<string>();
Items.Add("cherry");
Items.Add("cherry1");
Items.Add("cherry2");
Items.Add("cherry3");
Items.Add("cherry4");
Items.Add("wendy");
Items.Add("barry");
Items.Add("nico");
Items.Add("leo");
}
public Command<string> SearchCommand
{
get
{
return _searchCommand ?? (_searchCommand = new Command<string>(
obj => { },
obj => !string.IsNullOrEmpty(obj.ToString())));
}
}
public string SelectedItem
{
get
{
return _selecteditem;
}
set
{
_selecteditem = value;
RaisePropertyChanged("SelectedItem");
}
}
}
The viewmodelbase is one class that implementing INotifyPropertyChanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The screenshot:
You can use the 'Xamarin Chips' component.
for the reasons
it supports almost all layouts
has Input, Choice, Filter, and Action types of displays
you can select single, multiple choices or enter the new one
lots of UI customization options
https://help.syncfusion.com/xamarin/chips/overview
Regards,
N Baua

RadListView not responding to new items in an ObservableArray

I'm trying to render a list of images, using RadListView. Being that i had some bizarre behavior when the data was coming from a normal array, i decided to try ObservableArray, as recommended in the docs.(specifically tns-vue)
The problem is, that pushing a new item to the model, doesn't update the view. The item is there, but nothing is shown.
This is my RadListView:
<RadListView layout="grid" ref="newImages" for="image in newImages">
<v-template>
<ImageComponent
showDate="false"
:onLongPress="()=>{onLongPress(image)}"
:image="image"
></ImageComponent>
</v-template>
</RadListView>
The "newImages" array:
data() {
return {
newImages: new ObservableArray([]),
};
}
I add items to the array, using the camera plugin:
openGallery() {
var that = this;
console.log(that.newImages.length);
var context = imagepicker.create({ mode: "multiple" }); // use "multiple" for multiple selection
context
.authorize()
.then(function() {
return context.present();
})
.then(function(selection) {
const images = [];
selection.forEach(function(selected) {
const image = that.createNewFileSchema(selected._android);
images.push(image);
});
that.newImages.push(images)//This adds the images to the array, but UI doesn't respond to the change.
})
.catch(function(e) {
alert(e);
});
},
What could be the problem here?
Your pushing arrays to a data arrays, to make the virtual DOM notice these changes, you probably wan't to use a deep watcher, calling a method returning the updated array.
You would have the same problem with Objects, but then you would be able to use:
this.$set(<object>, <key>, <value>)
I'm unsure if there is a better way for arrays, but you could try a watcher as said
watch: {
newImages: {
handler: function(<value>, <oldValue>) {},
deep: true
}
}
UPDATED - You can use this.$set for arrays
https://v2.vuejs.org/v2/api/#Vue-set
/* this.$set(<Array>, <Index>, <Value>) */
this.$set(this.newImages, this.newImages.length, [...newArrWithImages])
This guy explains reactively updating arrays: Vuejs and Vue.set(), update array

How to order data on the fly

I'm a beginner with react native or firebase so I don't really know how to explain this but I have no idea on how to order received data from the database.
Let's say I have a database:
appname
items
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
-some_random_generated_string
-name: "someString"
-value: "999"
-type: 0
I've tried already with all order types and also setting .indexOn rule but haven't come to a solution. Also tried adding an id and then order with it and didn't come to a solution.
I guess this is accessing the database to get items so I also tried ordering them on the same line but nothing worked except limiting the amount of data.
let itemsRef = db.ref('items');
then I have:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
this.setState({ items });
});
}
and I'm adding like this:
let addItem= (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
});
};
Basically what I want to achieve is to display data in reversed order than it was added so the last one added would be shown on the top.
You could do it in two ways.
First simply call .reverse() on your current array. If you call the push method to add new items, usually the key that's assigned to each child garanties they are stored in chronological order. Therefore, calling it as such should be good enough:
componentDidMount() {
itemsRef.on('value', snapshot => {
let data = snapshot.val();
let items = Object.values(data);
items.rerverse();
this.setState({ items });
});
}
Though i don't know if let items = Object.values(data); garanties on every browser that your data are ordered as intended. By the way Object.values() opposed to Object.keys() is not supported on many old browsers, but if you're using it in ReactNative then it's fine.
If you want to be fully sure it's properly ordered, rewrite it as such:
componentDidMount() {
itemsRef.on('value', snapshot => {
let items = [];
// forEach always send you the children in the right order
snapshot.forEach(snap => {
items.push(snap.val())
})
items.rerverse();
this.setState({ items });
});
}
Finally another way to do it if you don't trust firebase's auto generated ids, is to add objects as such:
let addItem = (item, value, type) => {
db.ref('/items').push({
name: item,
value: value,
type: type,
timestamp: new Date().getTime()
});
};
and to use firebase's function orderByChild when you fetch the data, using timestamp as the child, and then .reverse() it again like we did previously.

React Native: Integration With Existing Apps

I'm new to react and I followed the tutorial about integrating existing apps open in the React Native Docs.
private ReactRootView mReactRootView;
.......
Bundle launchOptions = new Bundle();
launchOptions.putBoolean("test", true);
//mReactRootView.startReactApplication(mReactInstanceManager, "ThirdAwesomeComponent", launchOptions);
mReactRootView.startReactApplication(mReactInstanceManager, "ThirdAwesomeComponent", null); // Actual example
Is there a way to read launchOptions in the HelloWorld Component at index.android.js?
Also I have two activities from where I need to call the react native daemon and want to render two different layouts returned by the server.
How can I do that since currently I only have one:
AppRegistry.registerComponent('HelloWorld', () => HelloWorld);
The best way to do is doing something like,
Redirect to App.js from the index page using
AppRegistry.registerComponent("App",()=>App)
This will redirect to app
Then for rendering two different scenes based on server output. You can create a state variable and initialize it to be the default state.
in the render function of you component you can then check the state value and assign the layout as per your necessity.
Use something like
export default Class App extends Component{
constructor(props){
super(props)
this.state{
data1:false,
data2:true,
loaded:false,
}
}
//do all the fetching data to server here
componentWillMount(){
//after fetching the data to server
change the state as
this.setState({
data1:true,
data2:false,
loaded:true
})
}
render({
if(this.state.loaded && this.state.data1){
return(
//layout which you want to return
)
}else if( this.state.loaded && this.state.data2){
return(
//second layout code
)
}else{
return(
//can create a loading spinner here
<Text>Loading.....</Text>
)
}
})
}
Hope this helps
Cheers
Your launching options will be passed to the constructor of your component as props.
Just implement the constructor
constructor(props){
super(props)
// do stuff to pops dictionary
}

React Native - Navigator inconsistent across different methods

I am creating my first React Native App. I am trying to use the navigator object for navigating between different views.
In the below code snippet.
The openRecipe method written works perfectly but the goBack method throws an exception saying
undefined is not an object(evaluating this.props.navigator)
I haven't added any props to the Component Class, which I initially thought to be a problem, but since the OpenRecipe method works fine, I am confused on why goBack is throwing on exception which has the same method body as the openRecipe method.
If there were an issue with not including dependencies then it should have been consistent across both the methods.
Once it is sorted out,I am planning to use this.props.navigator.pop() to go back to the previous page.
openRecipe(data){
this.props.navigator.push({
id: 'RecipePage',
name: 'Recipe',
});
}
goBack(){
Alert.alert(
"Here Back!",
)
this.props.navigator.push({
id: 'RecipePage',
name: 'Recipe',
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<Text style={styles.title}>Recipe</Text>
<TouchableHighlight onPress={this.goBack}>
<Text style={styles.title} >BACK</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.dataSource}
renderRow={(data) =>
<TouchableHighlight onPress={() => this.openRecipe(data)}>
<View style={styles.article_container} >
<Text style={styles.article_title} >{data.title}</Text>
<Image style={styles.article_img}
source={{uri: data.image_link}}
/>
</View>
</TouchableHighlight>
}
/>
</View>
);
If your component is implemented as an ES6 class, the goBack method is not automatically bound to the this instance of your object, which React.createClass does automatically. The solution is to either pass in a "fat arrow" lambda as the onPress prop (e.g onPress={() => this.goBack()}), which will bind this to the value it has where the lambda is defined, or to bind it explicitly with onPress={this.goBack.bind(this)}
To elaborate, now that I'm not on a phone keyboard...
In javascript, the value of this depends on the context in which a function (or method) is called, not where it's defined. When a function is a property of an object (a method), and it's invoked as such, this has the value you probably expect; it's the parent object that contains the method.
const person = {
name: 'Shaurya',
sayHello() {
return "Hi " + this.name
}
}
person.sayHello() // -> "Hi Shaurya"
But if I store the sayHello function in a variable and call it from "outside" the object's context, the value of this will depend on where you're calling the function from. If you're running at the global scope (e.g inside a global function, or at a node repl), this will be the global object (where language builtins like Math live). Unless that happens to have a name property, you'll get undefined for this.name:
let sayHi = person.sayHello
sayHi() // -> "Hi undefined"
You can use the .apply method of the Function type to set the value of this to something else temporarily:
sayHi.apply(person) // -> "Hi Shaurya"
sayHi.apply({name: "Yusef"}) // -> "Hi Yusef"
sayHi() // -> still "Hi undefined"
Or, you can use .bind to set the value of this and make it persist:
var sayHiToPerson = person.sayHello.bind(person)
sayHiToPerson() // -> "Hi Shaurya"
The "fat arrow" lambdas introduced in ES6 capture the value of this, and no matter where you invoke it, this will have the same value as it did when the lambda was defined. That's why your second onPress handler works but the first one doesn't. Inside the body of () => this.openRecipe(data), this gets bound automatically to the value it had inside the .render method. But when you just pass this.goBack you lose that context, and this has a different value when the function is invoked by the event handling system.

Categories

Resources