Implementing Stripe payment in Eventyay Attendee

In Eventyay Attendee, getting tickets for events has always been a core function that we focus on. When searching for events based on location, autosuggestion based on user input really comes out as a great feature to increase the user experience. Let’s take a look at the implementation Why using Stripe?Implementing Stripe Payment in Eventyay AttendeeConclusionResources WHY USING STRIPE? There are many great APIs to be taken into consideration for making payments but we choose Stripe as one of our payment gateways because of simple implementations, detailed documentation, a good number of supported card type and good security support IMPLEMENTING STRIPE PAYMENT IN EVENTYAY ATTENDEE Step 1: Setup dependency in the build.gradle // Stripe implementation 'com.stripe:stripe-android:10.3.0' Step 2: Set up UI to take card information The information needed for making payments are Card Number, CVC, Expiration Date, which can be made with simple UI (EditText, Spinner,…). Stripe support getting information with CardInputWidget but we made a custom UI for that. Here is the UI we created. Step 3: Create a card and validate information Stripe has an object called Card, which takes card number, expiration date and CVC number as parameter to detect the card type and validate the card information with function .validateCard() PAYMENT_MODE_STRIPE -> { card = Card.create(rootView.cardNumber.text.toString(), attendeeViewModel.monthSelectedPosition, rootView.year.selectedItem.toString().toInt(), rootView.cvc.text.toString()) if (!card.validateCard()) { rootView.snackbar(getString(R.string.invalid_card_data_message)) false } else { true } } Step 4: Send the token to the server If card information is valid, we can create a token from the Card and then send it to the server. The token will act as the identifier of the card in order for the server to charge the payment and create tickets for the user.  private fun sendToken(card: Card) { Stripe(requireContext()) .createToken(card, BuildConfig.STRIPE_API_KEY, object : TokenCallback { override fun onSuccess(token: Token) { val charge = Charge(attendeeViewModel.getId().toInt(), token.id, null) attendeeViewModel.chargeOrder(charge) } override fun onError(error: Exception) { rootView.snackbar(error.localizedMessage.toString()) } }) } Step 5: So the rest is already handled by the server. Android application will then just receive the response from the server to see if the order is charged successfully or not. CONCLUSION With Stripe, user can easily make payments to get tickets for events. Stripe is a great payment gateway as it is really easy to implement in Android. Hopefully, this blog post will help you create a great shopping cart app or any kind of application that requires fast, simple and easy payments. RESOURCES Eventyay Attendee Pull Request on Stripe: https://github.com/fossasia/open-event-attendee-android/pull/1863 Documentation from Stripe for Android: https://stripe.com/docs/mobile/android

Continue ReadingImplementing Stripe payment in Eventyay Attendee

Integrating Redux In Settings

Settings page in SUSI.AI earlier implemented using Flux needed to be migrated to Redux, Redux integration eases handling of global state management. With Redux being integrated with Settings, the code could be split into smaller manageable components. Earlier implementation involved passing data to other Settings Tab and using Settings component as a state source. This resulted in Settings component getting more lines of code(~1300 LOC). After using Redux, only ~280 LOC is present in settings component file, and separate files for each settings tab view.  Code Integration Once user login, the App component fetches User Settings in ComponentDidMount. componentDidMount = () => { const { accessToken, actions } = this.props; window.addEventListener('offline', this.onUserOffline); window.addEventListener('online', this.onUserOnline); actions.getApiKeys(); if (accessToken) { actions.getAdmin(); actions.getUserSettings().catch(e => { console.log(e); }); } }; src/App.js The action getUserSettings fetch users settings from apis.getUserSettings and populates the store settings using SETTINGS_GET_USER_SETTINGS reducer function. Default States: The store consists of all the settings required for persisting data. const defaultState = { theme: 'light', server: 'https://api.susi.ai', enterAsSend: true, micInput: true, speechOutput: true, speechOutputAlways: false, speechRate: 1, speechPitch: 1, ttsLanguage: 'en-US', userName: '', prefLanguage: 'en-US', timeZone: 'UTC-02', customThemeValue: { header: '#4285f4', pane: '#f3f2f4', body: '#fff', composer: '#f3f2f4', textarea: '#fff', button: '#4285f4', }, localStorage: true, countryCode: 'US', countryDialCode: '+1', phoneNo: '', checked: false, serverUrl: 'https://api.susi.ai', backgroundImage: '', messageBackgroundImage: '', avatarType: 'default', devices: {}, }; reducers/settings.js Reducer action: SETTINGS_GET_USER_SETTINGS sets the settings store from API payload. The customThemeValue is an object, which is populated once the settings are fetched and the string is split into array. [actionTypes.SETTINGS_GET_USER_SETTINGS](state, { error, payload }) { const { settings, devices = {} } = payload; if (error || !settings) { return state; } const { theme = defaultState.theme, server, …. avatarType, } = settings; let { customThemeValue } = settings; const themeArray = customThemeValue ? customThemeValue.split(',').map(value => `#${value}`) : defaultState.customThemeValue; return { ...state, devices, server, serverUrl, theme, enterAsSend: enterAsSend === 'true', micInput: micInput === 'true', speechOutput: speechOutput === 'true', speechOutputAlways: speechOutputAlways === 'true', speechRate: Number(speechRate), speechPitch: Number(speechPitch), ttsLanguage, userName, prefLanguage, timeZone, countryCode, countryDialCode, phoneNo, checked: checked === 'true', backgroundImage, messageBackgroundImage, avatarType, customThemeValue: { header: themeArray[0], pane: themeArray[1], body: themeArray[2], composer: themeArray[3], textarea: themeArray[4], button: themeArray[5], }, }; }, reducers/settings.js Updating Settings Reduce function: The payload contains the updated state, the reducer returns a new state with payload appended. setUserSettings: createAction( actionTypes.SETTINGS_SET_USER_SETTINGS, returnArgumentsFn, ) reducers/settings.js Action: The setUserSettings action updates the settings store, once the user presses the Save Button in the Settings Tab. function mapStateToProps(store) { return { enterAsSend: store.settings.enterAsSend, }; } actions/settings.js Redux integration in the component Redux is used in ChatAppTab as: The local state of ChatAppTab is initialized with redux store state. Using mapStateToProps we can access enterAsSend object from the redux store. The connect function connects the React component with Redux store and injects props and actions into our component using HOC pattern. It injects data from the store and functions for dispatching actions to store. Injecting props in local components: function mapStateToProps(store) { return { enterAsSend: store.settings.enterAsSend, }; } components/Settings/ChatAppTab.react.js Once a user…

Continue ReadingIntegrating Redux In Settings

The Robotic Arm Controller Feature in PSLab Android Application.

Recently while working on PSLab Android Project, a new feature was implemented to add a controller for a Robotic Arm in the Android Application. In this blog, I will explain what this new feature is, how it has been implemented and what are some of the functionalities of it. What is Robotic Arm? Robotic Arm, as the name suggests is a small arm-like structure, which moves with the help of 4 servo motors connected to it. The image of the robotic arm is as under,  (Figure 1 : Robotic Arm) What is the Robotic Arm Controller Feature in Android App? As mentioned earlier, the robotic arm uses 4 servos for the movements. The aim of the controller feature in the Android app is to allow users to adjust the rotation of each servo with a very intuitive UI, so they can move the robotic arm as they wish. Further, in this blog, I will discuss UI, implementation and some cool features of the robotic Arm Controller. User Interface (figure 2: UI of the robotic arm controller) In the screenshot above, there are 4 circular controllers for each respective servo. The user can use the knob or enter the values using the keyboard by tapping on the value at the center of the knobs. Each value indicates the value in degrees the user wishes to move that servo. Timeline Below the 4 servo controllers is a black timeline. There are 60 boxes in each of the 4 timelines. These 60 boxes indicates the seconds. So basically if the user wants the robotic arm to perform some set of actions sequentially, using these timelines, users can set the rotation for each servo at each second, then user can use the play button on the red control panel to play this timeline and the app will send the degree value at each second to the respective servo. A filled timeline would look something like below,  (Figure 3: Timeline) As can be seen, there are different values for each servo at each second, so when the user starts the timeline, values at each second will be sent to the respective servos. servo4() function of the ScienceLab class is called to set values for all for servos at each second. How to add the values to the timeline? There is a small handle on the top right corner of each servo controller, user can long-press the handle and drag and drop the values to desired seconds for respective servos.  This functionality can be found in this video between 0:33 to 0:43 This feature uses Android’s drag and drops listener. The code for this drag and drop function is as under, private View.OnDragListener servo1DragListener = new View.OnDragListener() { @Override public boolean onDrag(View v, DragEvent event) { if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED) { View view = (View) event.getLocalState(); TextView text = view.findViewById(R.id.degreeText); if (view.getId() == R.id.servo_1) { ((TextView) v.findViewById(R.id.timeline_box_degree_text)).setText(text.getText()); } } return true; } }; Such drag listeners are created for each servo controller. Save Timeline Feature Suppose…

Continue ReadingThe Robotic Arm Controller Feature in PSLab Android Application.

Enhancing User Security With Server-Side Validation

Server-side validation of SUSI.AI is required for increasing the security of user against malicious users who can bypass client-side validations easily and submit dangerous input to SUSI.AI server. SUSI.AI uses both client-side and server-side validation for various authentication purposes. Client-side validates user input on the browser and provides a better user interaction. If the form is disabled on incorrect fields, it would save a round trip to the server, so the network traffic which will help your server perform better. Using client-side validation, an error message can be immediately shown before the user moves to the next field. Adding Recaptcha while on new registration, change password, and more than one login attempts help to reduce abusive traffic, spam, brute force attack. For making user accounts safe from getting hacked on SUSI.AI, a strong password is supposed to be at least 8 characters, at least one special character, one number and text characters are must. The username must have 5-51 characters. So as to prevent SUSI.AI admin panel from crashing. Code Integration Client side reCaptcha validation: onCaptchaSuccess = captchaResponse => { if (captchaResponse) { this.setState({ showCaptchaErrorMessage: false, captchaResponse, }); } }; Auth/Login/Login.react.js Once the user verifies captcha on client-side, a callback onCaptchaSuccess fires which receives a parameter captchaResponse. This captchaResponse is sent over to server-side with key g-recaptcha-response Validation for User Login, ReCaptcha needs to be shown if the user has tried to log in for more than 1 time. For this, sessionStorage needs to be maintained. sessionStorage has an expiration time, which expires once the user closes the tab. In the constructor of Login, the attempt is initialized with sessionStorage using key loginAttempts. Now, we have the correct value of loginAttempts and can update it further if the user tries to make more attempt. constructor(props) { super(props); this.state = { ... showCaptchaErrorMessage: false, attempts: sessionStorage.getItem('loginAttempts') || 0, captchaResponse: '', }; } Auth/Login/Login.react.js If the user closes the login dialog, we need to store the current attempts state on componentWillUnmount componentWillUnmount() { sessionStorage.setItem('loginAttempts', this.state.attempts); } Auth/Login/Login.react.js If attempts are less than 1, render the reCaptcha component and send g-recaptcha-response parameter to api.susi.ai/aaa/login.json API endpoint. Server-side reCaptcha Validation for user login The checkOneOrMoreInvalidLogins function in LoginService checks if the user has attempted to login in more than once. Access the accounting object and get identity and store it in accounting. private boolean checkOneOrMoreInvalidLogins(Query post, Authorization authorization, JsonObjectWithDefault permissions) throws APIException { Accounting accounting = DAO.getAccounting(authorization.getIdentity()); JSONObject invalidLogins = accouting.getRequests().getRequests(this.getClass().getCanonicalName()); long period = permissions.getLong("periodSeconds", 600) * 1000; // get time period in which wrong logins are counted (e.g. the last 10 minutes) int counter = 0; for(String key : invalidLogins.keySet()){ if(Long.parseLong(key, 10) > System.currentTimeMillis() - period) counter++; } if(counter > 0){ return true; } return false; } aaa/LoginService.java If user has attempted to login in more than once, get from g-recaptcha-response API parameter. Check if reCaptcha is verified using VerifyRecaptcha utility class. if(checkOneOrMoreInvalidLogins(post, authorization, permissions) ){ String gRecaptchaResponse = post.get("g-recaptcha-response", null); boolean isRecaptchaVerified = VerifyRecaptcha.verify(gRecaptchaResponse); if(!isRecaptchaVerified){ result.put("message", "Please verify recaptcha"); result.put("accepted", false); return…

Continue ReadingEnhancing User Security With Server-Side Validation

Implementing pagination with Retrofit in Eventyay Attendee

Pagination (Paging) is a common and powerful technique in Android Development when making HTTP requests or fetching data from the database. Eventyay Attendee has found many situations where data binding comes in as a great solution for our network calls with Retrofit. Let’s take a look at this technique. Problems without Pagination in Android DevelopmentImplementing Pagination with Kotlin with RetrofitResults and GIFConclusions PROBLEMS WITHOUT DATABINDING IN ANDROID DEVELOPMENT Making HTTP requests to fetch data from the API is a basic work in any kind of application. With the mobile application, network data usage management is an important factor that affects the loading performance of the app. Without paging, all of the data are fetched even though most of them are not displayed on the screen. Pagination is a technique to load all the data in pages of limited items, which is much more efficient IMPLEMENTING DATABINDING IN FRAGMENT VIEW Step 1:  Set up dependency in build.gradle // Paging implementation "androidx.paging:paging-runtime:$paging_version" implementation "androidx.paging:paging-rxjava2:$paging_version" Step 2:  Set up retrofit to fetch events from the API @GET("events?include=event-sub-topic,event-topic,event-type") fun searchEventsPaged( @Query("sort") sort: String, @Query("filter") eventName: String, @Query("page[number]") page: Int, @Query("page[size]") pageSize: Int = 5 ): Single<List<Event>> Step 3: Set up the DataSource DataSource is a base class for loading data in the paging library from Android. In Eventyay, we use PageKeyedDataSource. It will fetch the data based on the number of pages and items per page with our default parameters. With PageKeyedDataSource, three main functions loadInitial(), loadBefore(), loadAfter() are used to to load each chunks of data. class EventsDataSource( private val eventService: EventService, private val compositeDisposable: CompositeDisposable, private val query: String?, private val mutableProgress: MutableLiveData<Boolean> ) : PageKeyedDataSource<Int, Event>() { override fun loadInitial( params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Event> ) { createObservable(1, 2, callback, null) } override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Event>) { val page = params.key createObservable(page, page + 1, null, callback) } override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Event>) { val page = params.key createObservable(page, page - 1, null, callback) } private fun createObservable( requestedPage: Int, adjacentPage: Int, initialCallback: LoadInitialCallback<Int, Event>?, callback: LoadCallback<Int, Event>? ) { compositeDisposable += eventService.getEventsByLocationPaged(query, requestedPage) .withDefaultSchedulers() .subscribe({ response -> if (response.isEmpty()) mutableProgress.value = false initialCallback?.onResult(response, null, adjacentPage) callback?.onResult(response, adjacentPage) }, { error -> Timber.e(error, "Fail on fetching page of events") } ) } } Step 4: Set up the Data Source Factory DataSourceFactory is the class responsible for creating DataSource object so that we can create PagedList (A type of List used for paging) for events. class EventsDataSourceFactory( private val compositeDisposable: CompositeDisposable, private val eventService: EventService, private val query: String?, private val mutableProgress: MutableLiveData<Boolean> ) : DataSource.Factory<Int, Event>() { override fun create(): DataSource<Int, Event> { return EventsDataSource(eventService, compositeDisposable, query, mutableProgress) } } Step 5: Adapt the current change to the ViewModel.  Previously, events fetched in List<Event> Object are now should be turned into PagedList<Event>. sourceFactory = EventsDataSourceFactory( compositeDisposable, eventService, mutableSavedLocation.value, mutableProgress ) val eventPagedList = RxPagedListBuilder(sourceFactory, config) .setFetchScheduler(Schedulers.io()) .buildObservable() .cache() compositeDisposable += eventPagedList .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .distinctUntilChanged() .doOnSubscribe { mutableProgress.value = true }.subscribe({ val currentPagedEvents = mutablePagedEvents.value if…

Continue ReadingImplementing pagination with Retrofit in Eventyay Attendee

How to work with MPAndroidChart? – Neurolab Memory graph program mode

Memory graph mode Overview In Android App development, implementation of Android Charts play an integral part in developing database oriented apps, be it casual or professional apps. Data Analysis plays an integral part in various sectors and industries in today’s world. In sports, e.g- cricket, we see the use of charts like bar charts, line charts in various stages of the game to present the game and score analysis. In healthcare, we see the use of live real time graphs to show human health analysis like the heart rate, brain waves, etc. In the IT industry, professionals use graphical data analysis for their presentations in meetings and conferences. I have been working with FOSSASIA on the Neurolab Android App, where I had to work on this unique program mode called “Memory Graph” wherein I had to work extensively with MPAndroidChart library. While working, I understood the various use cases of charts and graphs on the Android platform. In this blog, I want to showcase the building of one part of the app with a proper tutorial. Tutorial In the Android SDK, we, the developers have been given the benefit of a tool known as GraphView. But this view being a very default and basic tool in the toolkit, has limitations in terms of customization, needs a lot of boilerplate code to set up the base to work upon. Also plotting real time graphs becomes a challenge while using the GraphView. In this tutorial, we will be focusing on the Memory Graph program mode of the app wherein we will be using an external library - MPAndroidChart to help us achieve our aim.                                                         Memory Graph       1. Firstly, open the app level build.gradle and implement the library dependency in there.                  implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'          Note - The library version may change depending upon the time you are reading     this blog. Refer here for the latest library updates. XML layout 1. The layout for our chart screen should be simple without any other components on the screen so that it is perfectly comfortable for the users to understand the graph and not get overwhelmed. Here, we are going to use a RelativeLayout as the parent to the Chart. For our Memory graph program mode, I worked with the LineChart view from the MPAndroidChart library. The xml code for the layout: <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.github.mikephil.charting.charts.LineChart android:id="@+id/brain_chart" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> Layout binding and Java code 1. In the onCreate method of our MemoryGraph Activity, we set the layout parameters as flags for our chart layout to use of the screen window. We find the LineChart from the layout by its id and bind it up using the private variable which is going to be used to set it up programmatically. Here is the code for the onCreate method: private LineChart graph; private Thread thread; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_memory_graph); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); graph = findViewById(R.id.mem_graph); } 2. We are going to create a method named initGraph to…

Continue ReadingHow to work with MPAndroidChart? – Neurolab Memory graph program mode

Creating Common Loading Component in SUSI.AI

A circular loading component appears whenever an asynchronous event takes time on the front-end, to show the user that content is yet to be fetched and processed. Creating Common Loading component eased the process of handling circular loading alignment. Using common loader decreased code repetition. There were major cases for loader component, mobile view, and desktop view. Why Styled-components for SUSI.AI? Painless maintenanceOn-demand CSS injection: The components being rendered in the page are kept on track and only those component’s styles are injected into the DOM. If we combine react-loadable or some other code splitting, the app becomes really performant.Styled components generate their own class name like sc-g3h4h6 and add it as an attribute to DOM, which avoids class name overlaps and misalignments.Styled components are easier to detect as they can be detected by linter and husky. Whereas, static CSS files are not detected, which leads to dead code in the codebase and make them hard to manage. Simple dynamic styling: adapting the styling of a component based on its props, without having to manually manage dozens of classes.Automatic vendor prefixingReuse, Reduce code written for CSSSupports media queries, which makes creating a responsive PWA easier  Let’s have a look at it’s implemented. Code Integration const Container = styled.div` display: flex; align-items: center; justify-content: center; ${props => props.height ? css` height: ${props => props.height + 'rem'}; @media (max-width: 512px) { height: ${props => props.height > 20 ? props.height - 15 + 'rem' : props.height + 'rem'}; } ` : css` margin: 19rem 0; height: 100%; @media (max-width: 512px) { margin: 14rem 0; } `} `; const CircularLoader = ({ height = 'auto', color = 'primary', size = 64 }) => { return ( <Container height={height}> <CircularProgress color={color} size={size} /> </Container> ); }; components/shared/CircularLoader.js The CircularLoader component by default should acquire size 64 width and size, and color primary i.e. SUSI.AI blue color. The container created using styled component, by default should be horizontally and vertically centered. This is used to handle the case where the content takes the whole width and height(100%) of page.  If we want the container to be of specific height(lesser than the page in which the Loader is being rendered), we pass in height props. For handling the mobile views, in case of height, 100%, margin-top and margin-bottom are reduced by 5rem. In case of height passed in through component, check if the height is greater(than 20) so that the mobile view user can see the footer as well, reduce height by 15rem. If the height is lesser than 20rem, the footer is in the viewport. Before PR, let's have a look at how the Loader was rendered inside component: {loading ? ( <LoadingContainer> <CircularProgress size={64} /> </LoadingContainer> ) : ( ... )} Using Common shared loader, {loading ? <CircularLoader height={27} /> : ... Settings/Settings.react.js Adding styled component across the SUSI.AI web app proved to be of great help, the mobile views were easily made using media queries. Components positioning logic could be changed based on props, and…

Continue ReadingCreating Common Loading Component in SUSI.AI

Importing files from local storage in PSLab Android application

This blog demonstrates how a user can import log files from local storage to the PSLab Android application for various instruments and play them. This functionality is really useful as users can share their log files and import them in their app. This blog mostly consists of my work in the PSLab Android repository. How to access local storage files? We here use the concept of implicit intent to access the local storage of the device and then generate the file from the received data URI. Implicit intents differ from explicit intents in a way that, they don’t give exact class or activity to be initialized through the intent, instead they provide the action to be performed and the class or activities are selected implicitly from the required action The code block is shown below.  private void selectFile() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("*/*"); startActivityForResult(intent, 100); } Here the Intent.ACTION_GET_CONTENT defines implicit intent. This intent opens the activity related to the action of GETTING CONTENT. The type of content is specified in the Intent.setType(<TYPE>). Since here the type is set to “*/*”, it will open all types of files. If we want only images we can set Type to “images”. startActivityForResult(intent, <REQUEST_CODE>) starts the file selection activity.  How to generate a file from received URI? Once the user selects a file from the file selection activity we can generate the selected file from the data passed in the callback function of startActivityForResult(). The data intent passed as a parameter to onActivityResult() callback contains data for the selected file. We can retrieve path, name, etc details of the selected file from this data intent. The code block for the same is given below. @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { if (requestCode == 100) { if (resultCode == RESULT_OK) { Uri uri = data.getData(); String path = uri.getPath(); path = path.replace("/root_path/", "/"); File file = new File(path); getFileData(file); } else Toast.makeText(this, this.getResources().getString(R.string.no_file_selected), Toast.LENGTH_SHORT).show(); } } Here we check for the requestCode, which we passed when calling the startActivityForResult() function. We further check if the result is valid and then generate the file from the file path we received in the data Intent. Once we get the path we can get the selected file using the following lines of code: String path = uri.getPath(); path = path.replace("/root_path/", "/"); File file = new File(path); How to get Data from the file? Once the file is generated, it is passed to a function getFileData(File file) to get data in the file to add to the logs of the selected device.  The main part of the getFileData function is given below. FileInputStream is = new FileInputStream(file); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String line = reader.readLine(); int i = 0; long block = 0, time = 0; while (line != null) { if (i != 0) { String[] data = line.split(","); try { time += 1000; BaroData baroData = new BaroData(time, block, Float.valueOf(data[2]), Double.valueOf(data[3]), Double.valueOf(data[4])); realm.beginTransaction(); realm.copyToRealm(baroData); realm.commitTransaction(); } catch…

Continue ReadingImporting files from local storage in PSLab Android application

Handle app links and apply unit tests in Open Event Attendee Application

The open event attendee is an android app which allows users to discover events happening around the world using the Open Event Platform. It consumes the APIs of the open event server to get a list of available events and can get detailed information about them. Users following links on devices have one goal in mind: to get to the content they want to see. As a developer, you can set up Android App Links to take users to a link's specific content directly in your app, bypassing the app-selection dialog, also known as the disambiguation dialog. Because Android App Links leverage HTTP URLs and association with a website, users who don't have your app installed go directly to content on your site. A unit test generally exercises the functionality of the smallest possible unit of code (which could be a method, class, or component) in a repeatable way. You should build unit tests when you need to verify the logic of specific code in your app. Why unit test cases?Setup app link intent in the appApply unit test casesConclusionResources Let’s analyze every step in detail. Why unit test cases? As it is already discussed what the app link intents are, and it is basically for providing a better user experience for the application. These are some following reason why unit test cases should use -  Rapid feedback on failures.Early failure detection in the development cycle.Safer code refactoring, letting you optimize code without worrying about regressions.Stable development velocity, helping you minimize technical debt. JUnit4 library is used for unit tests. Setup app link intent in the app Declare the frontend host according to build flavor in the app level gradle file: buildTypes { release { resValue "string", "FRONTEND_HOST", "eventyay.com" } debug { resValue "string", "FRONTEND_HOST", "open-event-fe.netlify.com" } } Handle the app link intent in Manifest file by adding intent filter under main activity decleartion: <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="@string/FRONTEND_HOST"/> </intent-filter> Manifest will through the intent in the main activity file. Now handle the intent in main activity: override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handleAppLinkIntent(intent) } override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) handleAppLinkIntent(intent) } private fun handleAppLinkIntent(intent: Intent?) { val uri = intent?.data ?: return val bundle = AppLinkUtils.getArguments(uri) val destinationId = AppLinkUtils.getDestinationId(uri) if (destinationId != null) { navController.navigate(destinationId, bundle) } } Here a new class/object AppLinkUtils is defined which will return destination fragment id and the argument/data according to the intent URI. Apply unit test cases: First, implement the libraries in the gradle file -  1. JUnit for unit tests, 2. Robolectric for using android classes in the test class: testImplementation 'junit:junit:4.12' testImplementation 'org.robolectric:robolectric:3.4.2' Create a test class for testing the app link functions and run it with RoboLectricTestRunner: private const val EVENT = "event" private const val RESET_PASSWORD = "resetPassword" private const val VERIFY_EMAIL = "verifyEmail" @RunWith(RobolectricTestRunner::class) class AppLinkUtilsTest { private fun getAppLink(type: String): Uri { return when (type) { EVENT -> Uri.parse("https://eventyay.com/e/5f6d3feb") RESET_PASSWORD -> Uri.parse("https://eventyay.com/reset-password?token=822980340478781748445098077144") VERIFY_EMAIL -> Uri.parse("https://eventyay.com/verify?token=WyJsaXZlLmhhcnNoaXRAaG") else ->…

Continue ReadingHandle app links and apply unit tests in Open Event Attendee Application

How to transfer data in files from different locations to app directory

In Android apps, we might have seen various instances in-app where we can import, export or save files. Now, where does the files go to or come from? There needs to be a specific folder or directory (maybe root) for a particular app which inturn contains the necessary files, the user has worked with from the app. In this blog, we will be learning about the backend of how to create directories and store files in them dynamically with proper content. Context I have been working with FOSSASIA on the project Neurolab-Android. In the app, we have various program modes, which has the option to save/record data. The saved data gets logged in a new file in the Neurolab directory/folder. Feel free to go ahead and explore the feature. The Save/Record feature in Neurolab can be found in the app bar or as an option in the drop down menu present in the app bar itself in any program mode. The feature becomes functional, once the data is imported with the import data feature which is also present in the app bar.                                               Figure: Demonstration of features in Neurolab Tutorial Now, starting off, there are apps out there wherein users can save files from different segments in the app and those saved files can be used by the app itself at other times as they belong to that app itself specifically. First off, we need to make sure we have a directory for our app, wherein the files will get stored. If the directory or folder is not present, it needs to be created. File directory = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + DIRECTORY_NAME); if (!directory.exists()) { try { directory.mkdir(); } catch (Exception e) { e.printStackTrace(); } } Now, once the directory is present, we can go ahead to keep the saved files in it. Also we will be implementing category-wise storage of the files. Simply put, we will be storing csv files in the CSV folder, xls files in the Excel folder, etc. In the below code example, we see the use case of CSV ‘category’. private void categoryWise() {File categoryDirectory = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + DIRECTORY_NAME + File.separator + category); if (!categoryDirectory.exists()) { try { categoryDirectory.mkdir(); } catch (Exception e) { e.printStackTrace(); } }} For saving a file in our app, we need to get (import) the file into our app. Once the file is imported, we can get the path of the file with the help of its URI. Let’s assign the path to a variable named ‘importedFilePath’. Then we place the imported file in the required category directory within our parent app directory depending and deciding upon the extension of the imported file. File importedFile = new File(importedFilePath); FilePathUtil.setupPath(); Date currentTime = Calendar.getInstance().getTime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String fileName = sdf.format(currentTime); File dst = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + DIRECTORY_NAME + File.separator + categoryDirectory + File.separator + fileName + ".$extension"); if (!dst.exists()) { try { categoryWise() transfer(importedFile, dst); } catch (IOException e) {…

Continue ReadingHow to transfer data in files from different locations to app directory