Implementing Tweet Search Suggestions in Loklak Wok Android

Loklak Wok Android not only is a peer harvester for Loklak Server but it also provides users to search tweets using Loklak’s API endpoints. To provide a better search tweet search experience to the users, the app provides search suggestions using suggest API endpoint. The blog describes how “Search Suggestions” is implemented. Third Party Libraries used to Implement Suggestion Feature Retrofit2: Used for sending network request Gson: Used for serialization, JSON to POJOs (Plain old java objects). RxJava and RxAndroid: Used to implement a clean asynchronous workflow. Retrolambda: Provides support for lambdas in Android. These libraries can be installed by adding the following dependencies in app/build.gradle android { …. // removes rxjava file repetations packagingOptions { exclude 'META-INF/rxjava.properties' } } dependencies { // gson and retrofit2 compile 'com.google.code.gson:gson:2.8.1' compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' // rxjava and rxandroid compile 'io.reactivex.rxjava2:rxjava:2.0.5' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0' }   To add retrolambda // in project's build.gradle dependencies { … classpath 'me.tatarka:gradle-retrolambda:3.2.0' } // in app level build.gradle at the top apply plugin: 'me.tatarka.retrolambda'   Fetching Suggestions Retrofit2 sends a GET request to search API endpoint, the JSON response returned is serialized to Java Objects using the models defined in models.suggest package. The models can be easily generated using JSONSchema2Pojo. The benefit of using Gson is that, the hard work of parsing JSON is easily handled by it. The static method createRestClient creates the retrofit instance to be used for network calls private static void createRestClient() { sRetrofit = new Retrofit.Builder() .baseUrl(BASE_URL) // base url : https://api.loklak.org/api/ .addConverterFactory(GsonConverterFactory.create(gson)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); }   The suggest endpoint is defined in LoklakApi interface public interface LoklakApi { @GET("/api/suggest.json") Observable<SuggestData> getSuggestions(@Query("q") String query); @GET("/api/suggest.json") Observable<SuggestData> getSuggestions(@Query("q") String query, @Query("count") int count); …. }   Now, the suggestions are obtained using fetchSuggestion method. First, it creates the rest client to send network requests using createApi method (which internally calls creteRestClient implemented above). The suggestion query is obtained from the EditText. Then the RxJava Observable is subscribed in a separate thread which is specially meant for doing IO operations and finally the obtained data is observed i.e. views are inflated in the MainUI thread. private void fetchSuggestion() { LoklakApi loklakApi = RestClient.createApi(LoklakApi.class); // rest client created String query = tweetSearchEditText.getText().toString(); // suggestion query from EditText Observable<SuggestData> suggestionObservable = loklakApi.getSuggestions(query); // observable created Disposable disposable = suggestionObservable .subscribeOn(Schedulers.io()) // subscribed on IO thread .observeOn(AndroidSchedulers.mainThread()) // observed on MainUI thread .subscribe(this::onSuccessfulRequest, this::onFailedRequest); // views are manipulated accordingly mCompositeDisposable.add(disposable); }   If the network request is successful onSuccessfulRequest method is called which updates the data in the RecyclerView. private void onSuccessfulRequest(SuggestData suggestData) { if (suggestData != null) { mSuggestAdapter.setQueries(suggestData.getQueries()); // data updated. } setAfterRefreshingState(); }   If the network request fails then onFailedRequest is called which displays a toast saying “Cannot fetch suggestions, Try Again!”. If requests are sent simultaneously and they fail, the previous message i.e. the previous toast is removed. private void onFailedRequest(Throwable throwable) { Log.e(LOG_TAG, throwable.toString()); if (mToast != null) { // checks if a previous toast is present mToast.cancel();…

Continue ReadingImplementing Tweet Search Suggestions in Loklak Wok Android

Know the Usage of all Emoji’s on Twitter with loklak Emoji Heatmap

Loklak apps page now has a new app in its store, Emoji Heatmap. This app can be used to see the usage of all the emoji’s in the tweets all over the world in the form of heatmap. So, the major difference between the emoji heatmap and emoji heatmapper apps are heatmapper shows the tweets related to specific search query whereas this heatmapper app, it displays all the tweets which contains emojis. How do the App fetches and stores the locations The emoji heatmap uses the loklak search API . The search API needs a query in order to search and output the JSON data. But this app takes no input from the user end to search any query. To make the search dynamic, we are using an existing JSON file from emojiHeatmapper app and loklak-fetcher-client javascript file. From the emoji.json file, we collect the each query item and search it using loklak-fetcher-client. The output json file which we get from loklak-fetcher-client is retrieved into the emojiHeatmap and we extract the location parameter. The location parameter is then stored into the “feature” option of open layers 3 maps. So, here in the emoji Heatmap app, we iterate over the emoji.json, get different search query each time when we search for it using loklak search API. Code which adds the location retrieved into feature $.getJSON("../emojiHeatmapper/emoji.json", function(json) { for (var i = 0; i < json.data.length; i++){ var query = json.data[i][1]; // Fetch loklak API data, and fill the vector loklakFetcher.getTweets(query, function(tweets) { for(var i = 0; i < tweets.statuses.length; i++) { if(tweets.statuses[i].location_point !== undefined){ // Creation of the point with the tweet's coordinates // Coords system swap is required: OpenLayers uses by default // EPSG:3857, while loklak's output is EPSG:4326 var point = new ol.geom.Point(ol.proj.transform(tweets.statuses[i].location_point, 'EPSG:4326', 'EPSG:3857')); vector.addFeature(new ol.Feature({ // Add the point to the data vector geometry: point, weight: 20 })); } } }); } });   The above function gets has two variables query and point. The query variable stores the data that is being retrieved from the emoji.json file each time it iterates and that query is being sent into the loklak-fetcher-client. Then the point variable is in which the location tracked using the loklak search API is converted into the co-ordinates system followed by the Open Layers 3. Then the point is added as a feature to the map vector. The map vector is the place where all the features are stored and then appended onto the map as a heatmap. Resources Check more about OpenLayer 3 maps implementation at: OpenLayers - Welcome About OpenLayers 3 heatmap feature: https://openlayers.org/en/latest/apidoc/ol.layer.Heatmap.html

Continue ReadingKnow the Usage of all Emoji’s on Twitter with loklak Emoji Heatmap

Utilizing Readiness Probes for loklak Dependencies in Kubernetes

When we use any application and fail to connect to it, we do not give up and retry connecting to it again and again. But in the reality we often face this kind of obstacles like application that break instantly or when connecting to an API or database that is not ready yet, the app gets upset and refuses to continue to work. So, something similar to this was happening with api.loklak.org. In such cases we can’t really re-write the whole application again every time the problem occurs.So for this we need to define dependencies of some kind that can handle the situation rather than disappointing the users of loklak app. Solution: We will just wait until a dependent API or backend of loklak is ready and then only start the loklak app. For this to be done, we used Kubernetes Health Checks. Kubernetes health checks are divided into liveness and readiness probes. The purpose of liveness probes are to indicate that your application is running. Readiness probes are meant to check if your application is ready to serve traffic. The right combination of liveness and readiness probes used with Kubernetes deployments can: Enable zero downtime deploys Prevent deployment of broken images Ensure that failed containers are automatically restarted Pod Ready to be Used? A Pod with defined readiness probe won’t receive any traffic until a defined request can be successfully fulfilled. This health checks are defined through the Kubernetes, so we don’t need any changes to be made in our services (APIs). We just need to setup a readiness probe for the APIs that loklak server is depending on. Here you can see the relevant part of the container spec you need to add (in this example we want to know when loklak is ready): readinessProbe: httpGet: path: /api/status.json port: 80 initialDelaySeconds: 30 timeoutSeconds: 3   Readiness Probes Updating deployments of loklak when something pushed into development without readiness probes can result in downtime as old pods are replaced by new pods in case of Kubernetes deployment. If the new pods are misconfigured or somehow broken, that downtime extends until you detect the problem and rollback. With readiness probes, Kubernetes will not send traffic to a pod until the readiness probe is successful. When updating a loklak deployment, it will also leave old one’s running until probes have been successful on new copy. That means that if loklak server new pods are broken in some way, they’ll never see traffic, instead old pods of loklak server will continue to serve all traffic for the deployment. Conclusion Readiness probes is a simple solution to ensure that Pods with dependencies do not get started before their dependencies are ready (in this case for loklak server). This also works with more than one dependency. Resources Code in loklak server: https://github.com/loklak/loklak_server . More about readiness and liveness probes at: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/ Kubernetes Health checks: https://kubernetes-v1-4.github.io/docs/user-guide/production-pods/#liveness-and-readiness-probes-aka-health-checks Blog posts related to kubernetes: https://www.ianlewis.org/en/tag/kubernetes. Tutorial for creating and using Pods and probes: https://github.com/googlecodelabs/orchestrate-with-kubernetes/blob/master/labs/monitoring-and-health-checks.md

Continue ReadingUtilizing Readiness Probes for loklak Dependencies in Kubernetes

Using NodeJS modules of Loklak Scraper in Android

Loklak Scraper JS implements scrapers of social media websites so that they can be used in other platforms, like Android or in a native Java project. This way there will be only a single source of scraper, as a result it will be easier to update the scrapers in response to the change in websites. This blog explains how Loklak Wok Android, a peer for Loklak Server on Android platform uses the Twitter JS scraper to scrape tweets. LiquidCore is a library available for android that can be used to run standard NodeJS modules. But Twitter scraper can’t be used directly, due to the following problems: 3rd party NodeJS libraries are used to implement the scraper, like cheerio and request-promise-native and LiquidCore doesn’t support 3rd party libraries. The scrapers are written in ES6, as of now LiquidCore uses NodeJS 6.10.2, which doesn’t support ES6 completely. So, if 3rd party NodeJS libraries can be included in our scraper code and ES6 can be converted to ES5, LiquidCore can easily execute Twitter scraper. 3rd party NodeJS libraries can be bundled into Twitter scraper using Webpack and ES6 can be transpiled to ES5 using Babel. The required dependencies can be installed using: $npm install --save-dev webpack $npm install --save-dev babel-core babel-loader babel-preset-es2015 Bundling and Transpiling Webpack does bundling based on the configurations provided in webpack.config.js, present in root directory of project. var fs = require('fs'); function listScrapers() { var src = "./scrapers/" var files = {}; fs.readdirSync(src).forEach(function(data) { var entryName = data.substr(0, data.indexOf(".")); files[entryName] = src+data; }); return files; } module.exports = { entry: listScrapers(), target: "node", module: { loaders: [ { loader: "babel-loader", test: /\.js?$/, query: { presets: ["es2015"], } }, ] }, output: { path: __dirname + '/build', filename: '[name].js', libraryTarget: 'var', library: '[name]', } };   Now let’s break the config file, the function listScrapers returns a JSONObject with key as name of scraper and value as relative location of scraper, ex: {   twitter: "./scrapers/twitter.js",    github: "./scrapers/github.js"    // same goes for other scrapers } The parameters in module.exports as described in the documentation of webpack for multiple inputs and to use the generated output externally: entry: Since a bundle file is required for each scraper we provide the  the JSONObject returned by listScrapers function. The multiple entry points provided generate multiple bundled files. target: As the bundled files are to be used in NodeJS platform,  “node” is set here. module: Using webpack the code can be directly transpiled while bundling, the end users don’t need to run separate commands for transpiling. module contains babel configurations for transpiling. output: options here customize the compilation of webpack path: Location where bundled files are kept after compilation, “__dirname” means the current directory i.e. root directory of the project. filename: Name of bundled file, “[name]“ here refers to the key of JSONObject provided in entry i.e. key of JSONObect returned from listScrapers. Example for Twitter scraper, the filename of bundled file will be “twitter.js”. libraryTarget: by default the functions or methods inside bundled files…

Continue ReadingUsing NodeJS modules of Loklak Scraper in Android

Adding React based World Mood Tracker to loklak Apps

loklak apps is a website that hosts various apps that are built by using loklak API. It uses static pages and angular.js to make API calls and show results from users. As a part of my GSoC project, I had to introduce the World Mood Tracker app using loklak’s mood API. But since I had planned to work on React, I had to go off from the track of typical app development in loklak apps and integrate a React app in apps.loklak.org. In this blog post, I will be discussing how I introduced a React based app to apps.loklak.org and solved the problem of country-wise visualisation of mood related data on a World map. Setting up development environment inside apps.loklak.org After following the steps to create a new app in apps.loklak.org, I needed to add proper tools and libraries for smooth development of the World Mood Tracker app. In this section, I’ll be explaining the basic configuration that made it possible for a React app to be functional in the angular environment. Pre-requisites The most obvious prerequisite for the project was Node.js. I used node v8.0.0 while development of the app. Instead of npm, I decided to go with yarn because of offline caching and Internet speed issues in India. Webpack and Babel To begin with, I initiated yarn in the app directory inside project and added basic dependencies - $ yarn init $ yarn add webpack webpack-dev-server path $ yarn add babel-loader babel-core babel-preset-es2015 babel-preset-react --dev   Next, I configured webpack to set an entry point and output path for the node project in webpack.config.js - module.exports = { entry: './js/index.js', output: { path: path.resolve('.'), filename: 'index_bundle.js' }, ... }; This would signal to look for ./js/index.js as an entry point while bundling. Similarly, I configured babel for es2015 and React presets - { "presets":[ "es2015", "react" ] }   After this, I was in a state to define loaders for module in webpack.config.js. The loaders would check for /\.js$/ and /\.jsx$/ and assign them to babel-loader (with an exclusion of node_modules). React After configuring the basic presets and loaders, I added React to dependencies of the project - $ yarn add react react-dom   The React related files needed to be in ./js/ directory so that the webpack can bundle it. I used the file to create a simple React app - import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render( <div>World Mood Tracker</div>, document.getElementById('app') );   After this, I was in a stage where it was possible to use this app as a part of apps.loklak.org app. But to do this, I first needed to compile these files and bundle them so that the external app can use it. Configuring the build target for webpack In apps.loklak.org, we need to have a file by the name of index.html in the app’s root directory. Here, we also needed to place the bundled js properly so it could be included in index.html at app’s root. HTML Webpack Plugin Using…

Continue ReadingAdding React based World Mood Tracker to loklak Apps

Visualising Tweet Statistics in MultiLinePlotter App for Loklak Apps

MultiLinePlotter app is now a part of Loklak apps site. This app can be used to compare aggregations of tweets containing a particular query word and visualise the data for better comparison. Recently there has been a new addition to the app. A feature for showing tweet statistics like the maximum number of tweets (along with date) containing the given query word and the average number of tweets over a period of time. Such statistics is visualised for all the query words for better comparison. Related issue: https://github.com/fossasia/apps.loklak.org/issues/236 Obtaining Maximum number of tweets and average number of tweets Before visualising the statistics we need to obtain them. For this we simply need to process the aggregations returned by the Loklak API. Let us start with maximum number of tweets containing the given keyword. What we actually require is what is the maximum number of tweets that were posted and contained the user given keyword and on which date the number was maximum. For this we can use a function which will iterate over all the aggregations and return the largest along with date. $scope.getMaxTweetNumAndDate = function(aggregations) { var maxTweetDate = null; var maxTweetNum = -1; for (date in aggregations) { if (aggregations[date] > maxTweetNum) { maxTweetNum = aggregations[date]; maxTweetDate = date; } } return {date: maxTweetDate, count: maxTweetNum}; } The above function maintains two variables, one for maximum number of tweets and another for date. We iterate over all the aggregations and for each aggregation we compare the number of tweets with the value stored in the maxTweetNum variable. If the current value is more than the value stored in that variable then we simply update it and keep track of the date. Finally we return an object containing both maximum number of tweets and the corresponding date.Next we need to obtain average number of tweets. We can do this by summing up all the tweet frequencies and dividing it by number of aggregations. $scope.getAverageTweetNum = function(aggregations) { var avg = 0; var sum = 0; for (date in aggregations) { sum += aggregations[date]; } return parseInt(sum / Object.keys(aggregations).length); } The above function calculates average number of tweets in the way mentioned before the snippet. Next for every tweet we need to store these values in a format which can easily be understood by morris.js. For this we use a list and store the statistics values for individual query words as objects and later pass it as a parameter to morris. var maxStat = $scope.getMaxTweetNumAndDate(aggregations); var avg = $scope.getAverageTweetNum(aggregations); $scope.tweetStat.push({ tweet: $scope.tweet, maxTweetCount: maxStat.count, maxTweetOn: maxStat.date, averageTweetsPerDay: avg, aggregationsLength: Object.keys(aggregations).length }); We maintain a list called tweetStat and the list contains objects which stores the query word and the corresponding values. Apart from plotting these statistics, the app also displays the statistics when user clicks on an individual treat present in the search record section. For this we filter tweetStat list mentioned above and get the required object corresponding to the query word the user selected bind it to angular…

Continue ReadingVisualising Tweet Statistics in MultiLinePlotter App for Loklak Apps

Developing MultiLinePlotter App for Loklak

MultiLinePlotter is a web application which uses Loklak API under the hood to plot multiple tweet aggregations related to different user provided query words in the same graph. The user can give several query words and multiple lines for different queries will be plotted in the same graph. In this way, users will be able to compare tweet distribution for various keywords and visualise the comparison. All the searched queries are shown under the search record section. Clicking on a record causes a dialogue box to pop up where the individual tweets related to the query word is displayed. Users can also remove a series from the plot dynamically by just pressing the Remove button beside the query word in record section. The app is presently hosted on Loklak apps site. Related issue - https://github.com/fossasia/apps.loklak.org/issues/225 Getting started with the app Let us delve into the working of the app. The app uses Loklak aggregation API to get the data. A call to the API looks something like this: http://api.loklak.org/api/search.json?q=fossasia&source=cache&count=0&fields=created_at A small snippet of the aggregation returned by the above API request is shown below. "aggregations": {"created_at": { "2017-07-03": 3, "2017-07-04": 9, "2017-07-05": 12, "2017-07-06": 8, }} The API provides a nice date v/s number of tweets aggregation. Now we need to plot this. For plotting Morris.js has been used. It is a lightweight javascript library for visualising data. One of the main features of this app is addition and removal of multiple series from the graph dynamically. How do we achieve that? Well, this can be achieved by manipulating the morris.js data list whenever a new query is made. Let us understand this in steps. At first, the data is fetched using angular HTTP service. $http.jsonp('http://api.loklak.org/api/search.json?callback=JSON_CALLBACK', {params: {q: $scope.tweet, source: 'cache', count: '0', fields: 'created_at'}}) .then(function (response) { $scope.getData(response.data.aggregations.created_at); $scope.plotData(); $scope.queryRecords.push($scope.tweet); }); Once we get the data, getData function is called and the aggregation data is passed to it. The query word is also stored in queryRecords list for future use. In order to plot a line graph morris.js requires a data object which will contain the required values for a series. Given below is an example of such a data object. data: [ { x: '2006', a: 100, b: 90 }, { x: '2007', a: 75, b: 65 }, { x: '2008', a: 50, b: 40 }, { x: '2009', a: 75, b: 65 }, ], For every ‘x’, ‘a’ and ‘b’ will be plotted. Thus two lines will be drawn. Our app will also maintain a data list like the one shown above, however, in our case, the data objects will have a variable number of keys. One key will determine the ‘x’ value and other keys will determine the ordinates (number of tweets). All the data objects present in the data list needs to be updated whenever a new search is done. The getData function does this for us. var value = $scope.tweet; for (date in aggregations) { var present = false; for (var i = 0;…

Continue ReadingDeveloping MultiLinePlotter App for Loklak

Adding Unit Tests for Services in loklak search

In Loklak search, it can be tricky to write tests for services as these services are customizable and not fixed. Therefore, we need to test every query parameter of the URL. Moreover, we need to test if service is parsing data in a correct manner and returns only data of type ApiResponse. In this blog here, we are going to see how to build different components for unit testing services. We will be going to test Search service in loklak search which makes Jsonp request to get the response from the loklak search.json API which are displayed as feeds on loklak search. We need to test if the service handles the response in a correct way and if the request parameters are exactly according to customization. Service to test Search service in loklak search is one of the most important component in the loklak search. SearchService is a class with a method fetchQuery() which takes parameter and sets up URL parameters for the search.json API of loklak. Now, it makes a JSONP request and maps the API response. The Method fetchQuery() can be called from other components with parameters query and lastRecord to get the response from the server based on a certain search query and the last record to implement pagination feature in loklak search. Now as the data is retrieved, a callback function is called to access the response returned by the API. Now, the response received from the server is parsed to JSON format data to extract data from the response easily. @Injectable() export class SearchService { private static readonly apiUrl: URL = new URL('https://api.loklak.org/api/search.json'); private static maximum_records_fetch = 20; private static minified_results = true; private static source = 'all'; private static fields = 'created_at,screen_name,mentions,hashtags'; private static limit = 10; private static timezoneOffset: string = new Date().getTimezoneOffset().toString();constructor( private jsonp: Jsonp ) { }// TODO: make the searchParams as configureable model rather than this approach. public fetchQuery(query: string, lastRecord = 0): Observable<ApiResponse> { const searchParams = new URLSearchParams(); searchParams.set('q', query); searchParams.set('callback', 'JSONP_CALLBACK'); searchParams.set('minified', SearchService.minified_results.toString()); searchParams.set('source', SearchService.source); searchParams.set('maximumRecords', SearchService.maximum_records_fetch.toString()); searchParams.set('timezoneOffset', SearchService.timezoneOffset); searchParams.set('startRecord', (lastRecord + 1).toString()); searchParams.set('fields', SearchService.fields); searchParams.set('limit', SearchService.limit.toString()); return this.jsonp.get(SearchService.apiUrl.toString(), { search: searchParams }) .map(this.extractData)}private extractData(res: Response): ApiResponse { try { return <ApiResponse>res.json(); } catch (error) { console.error(error); } } Testing the service Create a mock backend to assure that we are not making any Jsonp request. We need to use Mock Jsonp provider for this. This provider sets up MockBackend and wires up all the dependencies to override the Request Options used by the JSONP request. const mockJsonpProvider = { provide: Jsonp, deps: [MockBackend, BaseRequestOptions], useFactory: (backend: MockBackend, defaultOptions: BaseRequestOptions) => { return new Jsonp(backend, defaultOptions); } };   Now, we need to configure the testing module to isolate service from other dependencies. With this, we can instantiate services manually. We have to use TestBed for unit testing and provide all necessary imports/providers for creating and testing services in the unit test. describe('Service: Search', () => { let service: SearchService = null; let backend: MockBackend = null;…

Continue ReadingAdding Unit Tests for Services in loklak search

Auto-Refreshing Mode in loklak Media Wall

Auto-refreshing wall means that the request to the loklak server for the feeds must be sent after every few seconds and adding up new feeds in the media wall as soon as the response is received for a single session. For a nice implementation, it is also necessary to check if the new feeds are being received from the server and consequently, close the connection as soon as no feeds are received as to maintain session singularity. In this blog post, I am explaining how I implemented the auto-refreshing mode for media wall using tools like ngrx/store and ngrx/effects. Flow Chart The flowchart below explains the workflow of how the actions, effects and service are linked to create a cycle of events for auto-refreshing mode. It also shows up how the response is handled as a dependency for the next request. Since effects play a major role for this behaviour, we can say it as the “Game of Effects”. Working Effect wallSearchAction$: Assuming the Query for media wall has changed and ACTION: WALL_SEARCH has been dispatched, we will start from this point of time. Looking into the flowchart, we can see as soon the action WALL_SEARCH is dispatched, a effect needs to be created to detect the action dispatched.This effect customizes the query and sets up various configurations for search service and calls the service. Depending on whether the response is received or not, it either dispatches WallSearchCompleteSuccessAction or WallSearchCompleteFailAction respectively. Moreover, this effect is responsible for changing the route/location of the application. @Effect() wallSearchAction$: Observable<Action> = this.actions$ .ofType(wallAction.ActionTypes.WALL_SEARCH) .debounceTime(400) .map((action: wallAction.WallSearchAction) => action.payload) .switchMap(query => { const nextSearch$ = this.actions$.ofType(wallAction.ActionTypes.WALL_SEARCH).skip(1); const searchServiceConfig: SearchServiceConfig = new SearchServiceConfig();if (query.filter.image) { searchServiceConfig.addFilters(['image']); } else { searchServiceConfig.removeFilters(['image']); } if (query.filter.video) { searchServiceConfig.addFilters(['video']); } else { searchServiceConfig.removeFilters(['video']); }return this.apiSearchService.fetchQuery(query.queryString, searchServiceConfig) .takeUntil(nextSearch$) .map(response => { const URIquery = encodeURIComponent(query.queryString); this.location.go(`/wall?query=${URIquery}`); return new apiAction.WallSearchCompleteSuccessAction(response); }) .catch(() => of(new apiAction.WallSearchCompleteFailAction(''))); Property lastResponseLength: Looking into the flow chart, we can see that after WallSearchCompleteSuccessAction is dispatched, we need to check for the number of feeds in the response. If the number of feeds in the response is more than 0, we can continue to make a new request to the server. On the other hand, if no feeds are received, we need to close the connection and stop requesting for more feeds. This check is implemented using lastResponseLength state property of the reducer which maintains the length of the entities for the last response received. case apiAction.ActionTypes.WALL_SEARCH_COMPLETE_SUCCESS: { const apiResponse = action.payload;return Object.assign({}, state, { entities: apiResponse.statuses, lastResponseLength: apiResponse.statuses.length }); }   Effect nextWallSearchAction$: Now, we have all the information regarding if we should dispatch WALL_NEXT_PAGE_ACTION depending on the last response received. We need to implement an effect that detects WALL_SEARCH_COMPLETE_SUCCESS  keeping in mind that the next request should be made 10 seconds after the previous response is received. For this behaviour, we need to use debounceTime() which emits a value only after certain specified time period has passed. Here, debounce is set to 10000ms which is equal…

Continue ReadingAuto-Refreshing Mode in loklak Media Wall

Resource Injection Using ButterKnife in Loklak Wok Android

Loklak Wok Android being a sophisticated Android app uses a lot of views, and of those most are manipulated at runtime. In Android to play with a View or ViewGroup defined in XML at runtime requires developers to add the following line: (TypeOfView) parentView.findViewById(R.id.id_of_view);   This leads to lengthy code. And very often, more than one Views respond to a particular event. For example, hiding Views if a network request fails and showing a message to the user to “Try Again!”. Let’s say you have to hide 4 Views, are you going to do the following: view1.setVisibility(View.GONE); view2.setVisibility(View.GONE); view3.setVisibility(View.GONE); view4.setVisibility(View.GONE); textView.setVisibility(View.VISIBLE); // has "Try Again!" message. // more 5 lines of code when hiding textView and displaying 4 other Views   Surely not! And the old fashioned way to get a string value defined as a resource in string.xml String appName = getActivity().getResources().getString(R.id.app_name);   Surely, all this works good, but being a developer while working on a sophisticated app you would like to focus on the logic of the app, rather than scratching your head to debug whether you properly did a findViewById or not, did you typecast it to the proper View, or where did you miss to change the visibility of a view in response to an event. Well, all of this can be easily handled by using a library which provides you the dependency, here resources. All you need to do is just declare your resources, and that’s it, the library provides the resources to you, yes you don’t need to initialize it using findViewById. So let’s dive in and see how ButterKnife is used in Loklak Wok Android to handle these issues. Adding ButterKnife to Android Project In the app/build.gradle: dependencies { compile 'com.jakewharton:butterknife:8.6.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0' ... } Dealing with Views in Fragments When views are declared, BindView annotation is used with its parameter as the ID of the view, for example, views in TweetHarvestingFragment : @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.harvested_tweets_count) TextView harvestedTweetsCountTextView; @BindView(R.id.harvested_tweets_container) RecyclerView recyclerView; @BindView(R.id.network_error) TextView networkErrorTextView; NOTE: Views declared can’t be private. Once Views are declared, then it needs to be injected, it is done using ButterKnife.bind(Object target, View Source). Here in TweetHarvestingFragment the target will be the fragment itself and source i.e. the parent view will be rootView (obtained by inflating the layout file of fragment). All this needs to be done in onCreateView method View rootView = inflater.inflate(R.layout.fragment_tweet_harvesting, container, false); ButterKnife.bind(this, rootView); That’s it, we are done! The same paradigm can be used to bind views to a ViewHolder of a RecyclerView, as implemented in HarvestTweetViewHolder: @BindView(R.id.user_fullname) TextView userFullname; @BindView(R.id.username) TextView username; @BindView(R.id.tweet_date) TextView tweetDate; @BindView(R.id.harvested_tweet_text) TextView harvestedTweetTextView; public HarvestedTweetViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); }   Injecting resources like strings, dimensions, colors, drawables etc. is even easier, only the related annotation and ID needs to be provided. Example the string app_name is used in TweetHarvestingFragment to display the app name i.e. “Loklak Wok” in toolbar @BindString(R.string.app_name) String appName; // directly used inside onCreateView to set the title in toolbar toolbar.setTitlet(appName);  …

Continue ReadingResource Injection Using ButterKnife in Loklak Wok Android