Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/telereso/telereso

Universal library to control your application resources remotely with easy, plug and play implementation
https://github.com/telereso/telereso

android firebase flutter hacktoberfest ios localization react-native remote web

Last synced: about 2 months ago
JSON representation

Universal library to control your application resources remotely with easy, plug and play implementation

Awesome Lists containing this project

README

        

![Banner](doc/banner.svg)

[![jitpack](https://jitpack.io/v/telereso/telereso.svg)](https://jitpack.io/#telereso/telereso)
[![Version](https://img.shields.io/cocoapods/v/Telereso.svg?style=flat)](https://cocoapods.org/pods/Telereso)
[![Pub](https://img.shields.io/pub/v/telereso.svg)](https://pub.dartlang.org/packages/telereso)
[![npm react native](https://img.shields.io/npm/v/telereso.svg)](https://www.npmjs.com/package/telereso)
[![npm web](https://img.shields.io/npm/v/telereso-web.svg)](https://www.npmjs.com/package/telereso-web)

Table of contents:

* auto-gen TOC:
{:toc}

## Installation

[Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) depends on Firebase to
use [Remote Config](https://firebase.google.com/docs/remote-config/) for resource management

And [Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) for realtime changes (_optional_)

All you need to get started is make sure your project has setup
firebase ([check docs](https://firebase.google.com/docs/guides))

then just add [Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) dependency to your
project

### Dependencies

ApproachInstruction

Gradle


// At your root build.gradle
allprojects {
repositories {
// add JitPack repository
maven { url 'https://jitpack.io' }
jcenter()
google()
}
}

// At your app build.gradle

implementation "io.telereso:telereso:${version}"

Gradle (Kotlin DSL)


// At your root build.gradle
allprojects {
repositories {
// add JitPack repository
maven { url 'https://jitpack.io' }
jcenter()
google()
}
}

// At your app build.gradle

implementation("io.telereso:telereso:${version}")

IOS


pod 'Telereso'

Flutter


dependencies:
telereso: ^${flutterVersion}

React Native


npm install telereso

React


npm install telereso-web

## Samples & Examples

Nothing feels better than a snippet of code ready to be copied!
Check samples in this [repo](https://github.com/telereso/telereso/tree/master/Samples)

* [Android](https://github.com/telereso/telereso/tree/master/Samples/android)
* [IOS](https://github.com/telereso/telereso/tree/master/Samples/ios)
* [Flutter](https://github.com/telereso/telereso/tree/master/Samples/flutter)
* [React Native](https://github.com/telereso/telereso/tree/master/Samples/react-native)
* [Web](https://github.com/telereso/telereso/tree/master/Samples/web)

## Firebase

This section will show how to set up firebase remote config to be used
with [Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal)

### Strings

#### Steps

* Open [Firebase console](https://console.firebase.google.com/) then select Remote Config Dashboard
* Add new param called `strings`


![img.png](doc/add_strings.png)

* Add a json containing key/value params representing your strings resource's key name (same key name found in the
strings.xml), and it's value

![img.png](doc/param_value.png)


* Add to `Strings` group (this is optional but good practice)

![img.png](doc/add_group.png)


* Save and publish

#### Localization

[Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) supports localization using
local after the strings prefix `strings_`

To support other languages just add more params each containing a json with same keys (as in the strings version) but
with a translated value

ex: `strings_fr,strings_ar...etc`

_Android developers_ it will be the same local you add to your values dir `values-fr,values-ar...etc`

_Notice we are using `_` instead of `-` due to remote config limitations_

### Drawables

#### Steps

* Open [Firebase console](https://console.firebase.google.com/) then select Remote Config Dashboard
* Add new param called `drawables`
* Add a json containing key/value params representing your drawable resource's key name (same key name found in the
drawable dir), and it's value will be a url of your remote image

![img.png](doc/add_drawable.png)


* Add to `Drawables` group (this is optional but good practice)

![add_group](doc/add_group.png)


* Save and publish

#### Screens support

To support multiple screens sizes add parameters to different sizes `drawables_1x`, `drawables_2x`, `drawables_3x`

`1x` being the lowest resolution and `3x` the highest

### Final Result

![final_result](doc/final_result.png)

### Conditional Resources

Remote Config provide conditions to be applied to your params (strings,drawables),

This will add another layer of dynamic delivery, so if you would like new versions to have specific resources,
or
segment of users that clicked a button,

Or strings and icons to be shown on specific days (Holidays 🎊🥳🎉!)...etc


You can see how [Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) will help avoid
multiple app releases.



![img.png](doc/conditions_res.png)

### A/B Testing

One of the great feature about Remote config is the out of the
box [A/B testing](https://firebase.google.com/docs/ab-testing)

Since all our resources are indexed as params we could easily create experiments.

The following example show how we can test Drawer titles and see which one achieve higher conversion


![img.png](doc/ab_testing.png)
_This can be used for icons as well_

## Usage

There are different scenarios to work
with [Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) ,

Wither you are starting a fresh new application, or an already in production application with large code base

### Platforms
* **Android**
Follow docs [here in this page](https://telereso.io/#initialization) ,

You can use Telereso plugin to help you with the localization migration.

* **IOS**
Follow docs [here in this page](https://telereso.io/#initialization)

* **Flutter**
[Check package docs](https://pub.dev/packages/telereso#telereso)

* **React Native**
[Check package docs](https://www.npmjs.com/package/telereso)

* **Web**
[Check package docs](https://www.npmjs.com/package/telereso-web)

### Initialization

Initialization By default will not make api calls it just to set up resources,

If your app has a splash screen it would be a perfect place to do this, or on your custom application class

The `init` function has a call back you can listen to,

Or you could use the suspended version `suspendedInit` to make sure your app fetch the latest remote changes.

Skipping the Initialization will not cause crashes, but the app will not be able to use the remote version of the
resources,

So it is a way to disable remote functionality.

**Application Start**



Kotlin
Java
Swift
Dart
React Native
Web

{% highlight kotlin %}

```kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Telereso.init(this)
}
}
```

{% endhighlight kotlin %}


{% highlight java %}

```java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Telereso.init(this);
}
}
```

{% endhighlight java %}


{% highlight swift %}

```swift
class AppDelegate: NSObject, UIApplicationDelegate {

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Telereso.initialize()
return true
}
}
```

{% endhighlight swift %}


{% highlight dart %}

```dart
void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({
Key key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
Telereso.instance.init();
return MaterialApp();
}
}
```

{% endhighlight dart %}


{% highlight kotlin %}

```kotlin
export default class App extends React.Component {
render() {
return ();
}
}

Telereso.init(i18n);
```
{% endhighlight kotlin %}



{% highlight kotlin %}

```kotlin
import App from "./App";

import i18n from "./i18n";
import {Telereso} from "telereso-web";
import firebase from "firebase/app";

// Initialize Firebase
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_APIKEY,
authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID,
};
firebase.initializeApp(firebaseConfig);
Telereso.init(i18n,firebase);

ReactDOM.render(

,
document.getElementById('root')
);
```
{% endhighlight kotlin %}

**Splash Screen**


Kotlin
Java
Swift
React Native
Web

{% highlight kotlin %}

```kotlin
class SplashActivity : Activity {
private override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
Telereso.init(this) {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}
```

{% endhighlight kotlin %}


{% highlight java %}

```java
public class SplashActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Telereso.init(this, () -> {
startActivity(new Intent(this, MainActivity.class));
finish();
return null;
});
}
}
```

{% endhighlight java %}


{% highlight swift %}

```swift
struct ContentView: View {
@State private var result: Result?

func initTelereso(){
Telereso.initialize(){() -> Void in
result = Result.success(true)
}
}

var body: some View {
switch result {
case .success(_):
mainBody
case .failure(let error):
let _ = print(error)
mainBody
case nil:
ProgressView().onAppear(perform: initTelereso)
}
}
}
```

{% endhighlight swift %}

{% highlight kotlin %}

```kotlin
import i18n from './i18n';
import { Telereso } from 'telereso';

export default class App extends React.Component {
state = {
splashFinished: false
}
constructor(props) {
super(props);
Telereso.suspendedInit(i18n).then(() => {
this.setState({
splashFinished: true
})
});
}

render() {
return (this.state.splashFinished ? : Loading...);
}
}
```

{% endhighlight kotlin %}

{% highlight kotlin %}

```kotlin
import i18n from "./i18n";
import {Telereso} from "telereso-web";
import firebase from "firebase/app";

export default class App extends React.Component {

state = {
splashFinished: false
}

componentDidMount() {
Telereso.suspendedInit(i18n,firebase).then(() => {
this.setState({
splashFinished: true
})
});
}

render() {
return (this.state.splashFinished ? : Loading...);
}
}
```

{% endhighlight kotlin %}

**Full Options**


Kotlin
Java
Swift
React Native
Web

{% highlight kotlin %}

```kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Telereso.init(this)
.disableLog() // disable general and main logs
.enableStringLog() // enable strings logs to debug keys and fetched remote
.enableDrawableLog() // enable drawable logs to to debug keys and fetched remote
.enableRealTimeChanges() // enabled realtime changes , if realtime is disabled remote is cached up to 12 horus
}
}
```

{% endhighlight kotlin %}


{% highlight java %}

```java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Telereso.init(this)
.disableLog() // disable general and main logs
.enableStringLog() // enable strings logs to debug keys and fetched remote
.enableDrawableLog() // enable drawable logs to to debug keys and fetched remote
.enableRealTimeChanges(); // enabled realtime changes , if realtime is disabled remote is cached up to 12 horus
}
}
```

{% endhighlight java %}


{% highlight swift %}

```swift
class AppDelegate: NSObject, UIApplicationDelegate {

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
Telereso.enableRealTimeChanges().enableStringLog().initialize()
return true
}
}
```

{% endhighlight swift %}

{% highlight kotlin %}

```kotlin
import i18n from './i18n';
import { Telereso } from 'telereso';

export default class App extends React.Component {
state = {
splashFinished: false
}
constructor(props) {
super(props);
Telereso
.disableLog() // disable general logs
.enableStringsLog() // enable logs for string setup for debuging locals and remote setup
.enableDrawableLog() // enable drabel logs for debuging keys and urls fetched from remote
.setRemoteConfigSettings({minimumFetchIntervalMillis: 36000}) // if you have custome remote config settings provide them here
.enableRealTimeChanges() // to enable real time changes
.suspendedInit(i18n).then(() => {
this.setState({
splashFinished: true
})
});
}

render() {
return (this.state.splashFinished ? : Loading...);
}
}
```

{% endhighlight kotlin %}

{% highlight kotlin %}

```kotlin
import i18n from "./i18n";
import {Telereso} from "telereso-web";
import firebase from "firebase/app";

export default class App extends React.Component {

state = {
splashFinished: false
}

componentDidMount() {
Telereso
.disableLog() // disable genral and main logs
.enableStringsLog() // enalbe initialization strings logs to debug current local and remote fetch
.enableDrawableLog() // enable drawable logs
.enableRealTimeChanges() // enable real time changes , by default remote cache is 12 hours , once enalbed will be 1 sec
.suspendedInit(i18n,firebase).then(() => {
this.setState({
splashFinished: true
})
});
}

render() {
return (this.state.splashFinished ? : Loading...);
}
}
```

{% endhighlight kotlin %}

#### Add `RemoteViewInflater` (Android)

This inflater will make sure all the android application views that display strings or images have the remote
functionality,

The inflater will detect if you're setting the text in the xml directly like `andriod:text=@stirngs/user_name`

And use the remote version if it's found or default back to the original value

The inflater handles the following views :

* TextView
* EditText
* ImageView
* Button
* ImageButton
* FloatingActionButton
* BottomNavigationView
* NavigationView

you can use the inflater with `App Theme` or `Activity Theme`

*App Theme*

If your activities Does not use their own custom theme , add `RemoteViewInflater` directly to the app theme as
the `viewInflaterClass`

```xml

<item name="colorPrimary">@color/style_color_primary</item>
<item name="colorPrimaryDark">@color/style_color_primary_dark</item>
<item name="colorAccent">@color/style_color_accent</item>
<item name="colorControlHighlight">@color/fab_color_pressed</item>
<item name="viewInflaterClass">io.telereso.android.RemoteViewInflater</item>

```

*Activity Theme*

if your activity uses a custom theme add `RemoteViewInflater` to that theme

```xml

<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="viewInflaterClass">io.telereso.android.RemoteViewInflater</item>

```

### Dynamic Resources

Sometimes we set the resrouces programmatically depending on a view state like so
`title = getString(R.strings.title_home)`,

In this case we can use the Remote version of the function `getString()`
which is
`getRemoteString()`

This will make sure to use the remote version of the resource if found or default it to the original value

**Strings**



Kotlin
Java
Swift
Dart
React Native
Web

{% highlight kotlin %}

```kotlin
titleTextView.text = getRemoteString(R.strings.title_home)
```

{% endhighlight kotlin %}


{% highlight java %}

```java
titleTextView.setText(Telereso.getRemoteString(R.strings.title_home));
```

{% endhighlight java %}


{% highlight swift %}

```swift
//UIKit
label.text = Telereso.getRemoteString("title home")

//SwiftUI
VStack{
Text(Telereso.getRemoteString("title home"))
}
```

{% endhighlight swift %}


{% highlight dart %}

```dart
Widget build(BuildContext context) {
return Text(
RemoteLocalizations.of(context).appTitle,
);
}
```

{% endhighlight java %}


{% highlight kotlin %}

```kotlin
export default class MyComponent extends React.Component {
render() {
return (

{i18n.t('title_home')}

);
}
}
```

{% endhighlight kotlin %}


{% highlight kotlin %}

```kotlin
export default class MyComponent extends React.Component {
render() {
return (

{i18n.t('title_home')}

);
}
}
```

{% endhighlight kotlin %}

**Drawables**



Kotlin
Java
Dart
React Native
Web

{% highlight kotlin %}

```kotlin
imageView.setRemoteImageResource(R.id.icon)
```

{% endhighlight kotlin %}


{% highlight java %}

```java
Telereso.setRemoteImageResource(imageView,R.id.icon);
```

{% endhighlight java %}


{% highlight dart %}

```dart
Widget build(BuildContext context) {
return RemoteImage.asset("assets/icons/image.png");
}
```

{% endhighlight dart %}


{% highlight kotlin %}

```kotlin
import RemoteImage from 'telereso';

export default class MyComponent extends React.Component {
render() {
return (



);
}
}
```

{% endhighlight kotlin %}


{% highlight kotlin %}

```kotlin
import RemoteImage from 'telereso-web';
import logo from "./assets/images/img.png";

export default class MyComponent extends React.Component {
render() {
return (

);
}
}
```

{% endhighlight kotlin %}

#### Dynamic Resources (out of the box, only android)

If you have a large code base and have a lot of `getString()` and `setImageResource`,

And replacing them with a remote version is not an option,

You can override the activity's context with a `RemoteWrapperContext`

That will take care of the changes for you without any code changes.

**Important note** if your app supports both portrait and land scape you need to handle the configuration changes
manually,

Later versions of [Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) will address
this issue

Add the following to all your activities or your `BaseActivity`



Kotlin
Java

{% highlight kotlin %}

```kotlin
class MainActivity : Activity {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(RemoteContextWrapper(newBase));
}
}
```

{% endhighlight kotlin %}


{% highlight java %}

```java
public class MainActivity extends Activity {
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(new RemoteContextWrapper(newBase));
}
}
```

{% endhighlight java %}


### Realtime Changes

Who doesn't love to see changes happening in real time ?

[Telereso](https://telereso.io?utm_source=github&utm_medium=readme&utm_campaign=normal) support this optional
implantation with some extra steps.

_We recommend enabling this while in development mode only_

**Cloud function**

Create a cloud function to be triggered when updating remote config, you can follow [this setup doc](https://firebase.google.com/docs/remote-config/propagate-updates-realtime) to do so,

_PS: only follow the cloud function part_


package.json

```json
{
"name": "sample-firebase-remoteconfig",
"version": "0.0.1",
"dependencies": {
"firebase-admin": "^9.4.2",
"firebase-functions": "^3.13.1"
}
}
```


index.js

```javascript
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

exports.pushConfig = functions.remoteConfig.onUpdate(versionMetadata => {
// Create FCM payload to send data message to PUSH_RC topic.
const payload = {
topic: "TELERESO_PUSH_RC",
data: {
"TELERESO_CONFIG_STATE": "STALE"
}
};
// Use the Admin SDK to send the ping via FCM.
return admin.messaging().send(payload).then(resp => {
console.log(resp);
return null;
});
});
```

_Notice the topic : **TELERESO_PUSH_RC** and data **TELERESO_CONFIG_STATE** has to the same_

**Client**

In your android project add th following code in your `MyFirebaseMessagingService`:




Kotlin
Java
Dart
React Native

{% highlight kotlin %}

```kotlin
class MyFirebaseMessagingService: FirebaseMessagingService() {
override fun onNewToken(token: String) {
if (BuildConfig.DEBUG)
Telereso.subscriptToChanges()
}
override fun onMessageReceived(remoteMessage:RemoteMessage) {
if (BuildConfig.DEBUG && Telereso.handleRemoteMessage(getApplicationContext(), remoteMessage)) return
// your logic
}
}
```
{% endhighlight kotlin %}


{% highlight java %}

```java
public class MyFirebaseMessagingService extends FirebaseMessagingService {
@Override
public void onNewToken(String token) {
if (BuildConfig.DEBUG)
Telereso.subscriptToChanges();
}
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
if (BuildConfig.DEBUG && Telereso.handleRemoteMessage(getApplicationContext(), remoteMessage))
return;
// your logic
}
}
```
{% endhighlight java %}

{% highlight dart %}

```dart
class _HomePageState extends RemoteState<_HomePage> {
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();

static Future myBackgroundMessageHandler(
Map message) async {
// put your normal logic
}

@override
void initState() {
super.initState();
_firebaseMessaging.configure(
onMessage: (message) async {
if (await Telereso.instance.handleRemoteMessage(message)) return;
// put your normal logic
},
onBackgroundMessage: myBackgroundMessageHandler,
onLaunch: (message) async {},
onResume: (message) async {},
);
Telereso.instance.subscribeToChanges();
}
}
```
{% endhighlight dart %}

{% highlight kotlin %}

```kotlin
import RemoteComponent from 'telereso';

export default class MyComponent extends RemoteComponent {
render() {
return (

{i18n.t('title_home')}

);
}
}
```
{% endhighlight kotlin %}


## Telereso API

Here are tables to help you use the library.

### Kotlin

|Function|Description|
|--------|-----------|
|`init(Context,finishCallback)`|setup resources to be used,finishCallback will be called as soon resources are ready, also will fetchAndActivate Remote config but will not block the init (finishCallback will be called before the fetch finishes)|
|`suspendedInit(Context,finishCallback)`|used inside other suspended functions or coroutines, it will fetchAndActivate Remote config then setup resources)|
|`Context.getRemoteString(R.string.)`|return remote string or original value|
|`View.getRemoteString(R.string.)`|return remote string or original value|
|`View.getRemoteString(R.string.)`|return remote string or original value|
|`ImageView.setRemoteImageResource(R.string.)`|set remote image resource or the original value|

### Java

|Function|Description|
|--------|-----------|
|`Telereso.init(Context,finishCallback)`|setup resources to be used,finishCallback will be called as soon resources are ready, also will fetchAndActivate Remote config but will not block the init (finishCallback will be called before the fetch finishes)|
|`Telereso.getRemoteString(Context, R.string.)`|return remote string or original value|
|`Teleresoset.getRemoteImageResource(ImageView,R.string.)`|set remote image resource or the original value|

## Getting Help

To report bugs, please use the GitHub project.

* Project Page: [https://github.com/telereso/telereso](https://github.com/telereso/telereso)
* Reporting Bugs: [https://github.com/telereso/telereso/issues](https://github.com/telereso/telereso/issues)