SlideShare a Scribd company logo
1 of 92
Download to read offline
StoreDataLoadingMadeEasy-ish
Mike Nakhimovich - NY Times
We ❤Making Apps
We ❤Open Source
Why?Open Source Makes Life Easier
Networking is easier:
HTTPURLCONNECTION
Volley
Retrofit/ Okhttp
Storage is Easier
SharedPreferences
Firebase
Realm
SqlBrite/SqlDelight
Parsing is Easier
JSONObject
Jackson
Gson
Moshi
Fetching, Persisting &
Parsing datahas become
easy
What's NOT Easy?
DATALOADINGEveryone does itdifferently
Whatis DataLoading?
TheActofgetting
datafroman external
systemtoauser'sscreen
Loading is
complicated
Rotation Handling isa special
snowflake
NewYorkTimes builtStoreto
simplifydataloading
github.com/NYTimes/store
OUR GOALS
Datashould survive
configuration changes
agnostic ofwhere itcomes from or
howitis needed
Activitiesand
presenters should
stopretaining MBs
ofdata
Offline as configuration
Cachingas standard,
notan exception
API should be
simple enough for
an internto use,yet
robust enough
for everydataload.
Howdowework
with DataatNY
Times?
80% case:
Need data, don'tcare
iffresh or cached
Need fresh data
background
updates &
pull to refresh
Requests needto be
async & reactive
Datais dependenton
each other,
DataLoading should
betoo
Performance is
importantNewcalls should hook into in flight
responses
Parsing should be
done efficientlyand
minimallyParse onceand cachethe resultfor future calls
Let's Use Repository
PatternBy creating reactiveand persistent
DataStores
Repository Pattern
Separatethe logicthatretrievesthe
dataand maps ittothe [view] model
fromthe [view] logicthatacts onthe
model.
The repositorymediates betweenthe
datalayerandthe [view] layer ofthe
application.
WhyRepository?
Maximizetheamountof
codethatcan betested
withautomation by
isolatingthe datalayer
Why
Repository?
Makes iteasierto have
multiple devsworking on
same feature
Why
Repository?
Datasource from many
locationswillbe centrally
managedwith consistent
access rulesand logic
Our Implementation
https://github.com/NYTimes/Store
WhatisaStore?
Aclassthatmanagesthe
fetching, parsing,and
storage ofaspecific data
model
Tell a Store:
Whatto fetch
Tell a Store:
Whatto fetch
Whereto cache
Tell a Store:
Whatto fetch
Whereto cache
Howto parse
Tell a Store:
Whatto fetch
Whereto cache
Howto parse
The Store handles flow
Storesare Observable
Observable<T> get( V key);
Observable<T> fetch(V key)
Observable<T> stream()
void clear(V key)
Let's see howstores
helped usachieve
our goalsthrougha
PizzaExample
Getis for Handling our 80% Use Case
public final class PizzaActivity {
Store<Pizza, String> pizzaStore;
void onCreate() {
store.get("cheese")
.subscribe(pizza ->.show(pizza));
}
}
Howdoes itflow?
On Configuration Change
You onlyneedto Save/Restoreyour !to get
your cached "
public final class PizzaActivity {
void onRecreate(String topping) {
store.get(topping)
.subscribe(pizza ->.show(pizza));
}
}
Please do notserializeaPizza
Itwould beverymessy
Whatwe gain?:Fragments/Presenters don'tneedto be
retained
NoTransactionTooLargeExceptions
Onlykeys needto be Parcelable
Efficiencyis Important:
for(i=0;i<20;i++){
store.get(topping)
.subscribe(pizza -> getView()
.setData(pizza));
}
ManyconcurrentGetrequestswillstillonly
hitnetwork once
Fetching NewDatapublic class PizzaPresenter {
Store<Pizza, String> store;
void onPTR() {
store.fetch("cheese")
.subscribe(pizza ->
getView().setData(pizza));
}
}
Howdoes Store routethe request?
Fetchalsothrottles multiple requestsand
broadcastresult
Stream listens for events
public class PizzaBar {
Store<Pizza, String> store;
void showPizzaDone() {
store.stream()
.subscribe(pizza ->
getView().showSnackBar(pizza));
}
}
How do We build a
Store?
By implementing interfaces
Interfaces
Fetcher<Raw, Key>{
Observable<Raw> fetch(Key key);
}
Persister<Raw, Key>{}
Observable<Raw> read(Key key);
Observable<Boolean> write(Key key, Raw raw);
}
Parser<Raw, Parsed> extends Func1<Raw, Parsed>{
Parsed call(Raw raw);
}
Fetcher defines how a Store will get new data
Fetcher<Pizza,String> pizzaFetcher =
new Fetcher<Pizza, String>() {
public Observable<Pizza> fetch(String topping) {
return pizzaSource.fetch(topping);
};
Becoming Observable
Fetcher<Pizza,String> pizzaFetcher =
topping ->
Observable.fromCallable(() -> client.fetch(topping));
Parsers helpwith
Fetchersthatdon't
returnViewModels
Some ParsersTransform
Parser<Pizza, PizzaBox> boxParser = new Parser<>() {
public PizzaBox call(Pizza pizza) {
return new PizzaBox(pizza);
}
};
Others read Streams
Parser<BufferedSource, Pizza> parser = source -> {
InputStreamReader reader =
new InputStreamReader(source.inputStream());
return gson.fromJson(reader, Pizza.class);
}
MiddleWare is forthe commonJSON cases
Parser<BufferedSource, Pizza> parser
= GsonParserFactory.createSourceParser(gson,Pizza.class)
Parser<Reader, Pizza> parser
= GsonParserFactory.createReaderParser(gson,Pizza.class)
Parser<String, Pizza> parser
= GsonParserFactory.createStringParser(gson,Pizza.class)
'com.nytimes.android:middleware:CurrentVersion'
'com.nytimes.android:middleware:jackson:CurrentVersion'
'com.nytimes.android:middleware-moshi:CurrentVersion'
Data FlowwithaParser
Adding Offline with Persisters
File System Record Persister
FileSystemRecordPersister.create(
fileSystem,key -> "pizza"+key, 1, TimeUnit.DAYS);
File System is KISS storage
interface FileSystem {
BufferedSource read(String var) throws FileNotFoundException;
void write(String path, BufferedSource source) throws IOException;
void delete(String path) throws IOException;
}
compile 'com.nytimes.android:filesystem:CurrentVersion'
Don'tlike our persisters?
No Problem! You
can implement your
own
Persister interfaces
Persister<Raw, Key> {
Observable<Raw> read(Key key);
Observable<Boolean> write(Key key, Raw raw);
}
Clearable<Key> {
void clear(Key key);
}
RecordProvider<Key> {
RecordState getRecordState( Key key);
DataFlowwithaPersister
We have our components!
Let's buildand openastore
MultiParser
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
Store Builder
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
Add Our Parser
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
NowOur Persister
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
And Our Parsers
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
.parsers(parsers)
Timeto OpenaStore
List<Parser> parsers=new ArrayList<>();
parsers.add(boxParser);
parsers.add(GsonParserFactory.createSourceParser(gson, Pizza.class));
Store<Pizza, String> pizzaStore =
StoreBuilder<String,BufferedSource,Pizza> parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.persister( FileSystemRecordPersister.create( fileSystem,
key -> "prefix"+key, 1, TimeUnit.DAYS))
.parsers(parsers)
.open();
Configuring caches
Configuring MemoryPolicy
StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.memoryPolicy(MemoryPolicy
.builder()
.setMemorySize(10)
.setExpireAfter(24)
.setExpireAfterTimeUnit(HOURS)
.build())
.open()
Refresh On Stale - BackFillingthe Cache
Store<Pizza, String> pizzaStore = StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.parsers(parsers)
.persister(persister)
.refreshOnStale()
.open();
Network Before Stales Policy
Store<Pizza, String> pizzaStore = StoreBuilder
.<String, BufferedSource, Pizza>parsedWithKey()
.fetcher(topping -> pizzaSource.fetch(topping))
.parsers(parsers)
.persister(persister)
.networkBeforeStale()
.open();
Stores inthewild
Intern Project: BestSellers List
public interface Api {
@GET
Observable<BufferedSource>
getBooks(@Path("category") String category);
@Provides
@Singleton
Store<Books, BarCode> provideBooks(FileSystem fileSystem,
Gson gson, Api api) {
return StoreBuilder.<BarCode, BufferedSource, Books>
parsedWithKey()
.fetcher(category -> api.getBooks(category))
.persister(SourcePersisterFactory.create(fileSystem))
.parser(GsonParserFactory.createSourceParser(gson, Books.class))
.open();
}
public class BookActivity{
...
onCreate(...){
bookStore
.get(category)
.subscribe();
}
Howdo books getupdated?
public class BackgroundUpdater{
...
bookStore
.fresh(category)
.subscribe();
}
DataAvailablewhen screen needs it
UI calls get, background services call fresh
DependentCalls
Simple case Using RxJavaOperators
MapStore resulttoanother Store Result
public Observable<SectionFront> getSectionFront(@NonNull final String name) {
return feedStore.get()
.map(latestFeed -> latestFeed.getSectionOrBlog(name))
.flatMap(section -> sfStore.get(SectionFrontId.of(section)));
}
More Complicated Example
Video Store Returns singlevideos,
PlaylistStore returns playlist,
Howdowe combine?
Relationships by overriding
Get/Fetch
Step1: CreateVideo Store
Store<Video, Long> provideVideoStore(VideoFetcher fetcher,
Gson gson) {
return StoreBuilder.<Long, BufferedSource, Video>parsedWithKey()
.fetcher(fetcher)
.parser(GsonParserFactory
.createSourceParser(gson, Video.class))
.open();
}
Step 2: Create PlaylistStore
public class VideoPlaylistStore extends RealStore<Playlist, Long> {
final Store<CherryVideoEntity, Long> videoStore;
public VideoPlaylistStore(@NonNull PlaylistFetcher fetcher,
@NonNull Store<CherryVideoEntity, Long> videoStore,
@NonNull Gson gson) {
super(fetcher, NoopPersister.create(),
GsonParserFactory.createSourceParser(gson, Playlist.class));
this.videoStore = videoStore;
}
Step 3: Override store.get()
public class VideoPlaylistStore extends RealStore<Playlist, Long> {
final Store<CherryVideoEntity, Long> videoStore;
@Override
public Observable<Playlist> get(Long playlistId) {
return super.get(playlistId)
.flatMap(playlist ->
from(playlist.videos())
.concatMap(video -> videoStore.get(video.id()))
.toList()
.map(videos ->
builder().from(playlist)
.videos(videos)
.build()));
}
Listening for changes
Step1: Subscribeto Store, Filterwhatyou need
sectionFrontStore.subscribeUpdates()
.observeOn(scheduler)
.subscribeOn(Schedulers.io())
.filter(this::sectionIsInView)
.subscribe(this::handleSectionChange);
Step2: Kick offafresh requesttothatstore
public Observable<SectionFront> refreshAll(final String sectionName) {
return feedStore.get()
.flatMapIterable(this::updatedSections)
.map(section->sectionFrontStore.fetch(section))
latestFeed -> fetch(latestFeed.changedSections));
}
Bonus: Howwe made Store
RxJavaforAPIand FunctionalNiceties
public Observable<Parsed> get(@Nonnull final Key key) {
return Observable.concat(
cache(key),
fetch(key)
).take(1);
}
public Observable<Parsed> getRefreshing(@Nonnull final Key key) {
return get(key)
.compose(repeatOnClear(refreshSubject, key));
}
GuavaCaches For MemoryCache
memCache = CacheBuilder
.newBuilder()
.maximumSize(maxSize())
.expireAfterWrite(expireAfter(),timeUnit())
.build();
In FlightThrottlerToo
inflighter.get(key, new Callable<Observable<Parsed>>() {
public Observable<Parsed> call() {
return response(key);
}
})
compile 'com.nytimes.android:cache:CurrentVersion'
Why GuavaCaches?
Theywillblockacrossthreads saving precious MBs ofdownloads
when making same requests in parallel
FileSystem:
Builtusing OKIOtoallowstreaming
from Networkto disk & diskto parser
On Fresh InstallNYTimesappsuccessfullydownloadsand caches
dozens ofmbs ofdata
Would love
contributions &
feedback!thanks for listening

More Related Content

What's hot

Appengine Java Night #2b
Appengine Java Night #2bAppengine Java Night #2b
Appengine Java Night #2b
Shinichi Ogawa
 
Appengine Java Night #2a
Appengine Java Night #2aAppengine Java Night #2a
Appengine Java Night #2a
Shinichi Ogawa
 
Enterprise Guice 20090217 Bejug
Enterprise Guice 20090217 BejugEnterprise Guice 20090217 Bejug
Enterprise Guice 20090217 Bejug
robbiev
 

What's hot (20)

Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech TalkContract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
Contract-driven development with OpenAPI 3 and Vert.x | DevNation Tech Talk
 
End to end todo list app with NestJs - Angular - Redux & Redux Saga
End to end todo list app with NestJs - Angular - Redux & Redux SagaEnd to end todo list app with NestJs - Angular - Redux & Redux Saga
End to end todo list app with NestJs - Angular - Redux & Redux Saga
 
Appengine Java Night #2b
Appengine Java Night #2bAppengine Java Night #2b
Appengine Java Night #2b
 
Appengine Java Night #2a
Appengine Java Night #2aAppengine Java Night #2a
Appengine Java Night #2a
 
Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2Intro to Retrofit 2 and RxJava2
Intro to Retrofit 2 and RxJava2
 
#ajn3.lt.marblejenka
#ajn3.lt.marblejenka#ajn3.lt.marblejenka
#ajn3.lt.marblejenka
 
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
[Srijan Wednesday Webinar] Rails 5: What's in It for Me?
 
Retrofit
RetrofitRetrofit
Retrofit
 
Java Libraries You Can’t Afford to Miss
Java Libraries You Can’t Afford to MissJava Libraries You Can’t Afford to Miss
Java Libraries You Can’t Afford to Miss
 
FullStack Reativo com Spring WebFlux + Angular
FullStack Reativo com Spring WebFlux + AngularFullStack Reativo com Spring WebFlux + Angular
FullStack Reativo com Spring WebFlux + Angular
 
Android getting started
Android getting startedAndroid getting started
Android getting started
 
Durable functions
Durable functionsDurable functions
Durable functions
 
iOS Keychain by 흰, 민디
iOS Keychain by 흰, 민디iOS Keychain by 흰, 민디
iOS Keychain by 흰, 민디
 
Enterprise Guice 20090217 Bejug
Enterprise Guice 20090217 BejugEnterprise Guice 20090217 Bejug
Enterprise Guice 20090217 Bejug
 
Android httpclient php_mysql
Android httpclient php_mysqlAndroid httpclient php_mysql
Android httpclient php_mysql
 
第一次用Parse就深入淺出
第一次用Parse就深入淺出第一次用Parse就深入淺出
第一次用Parse就深入淺出
 
Parse Advanced
Parse AdvancedParse Advanced
Parse Advanced
 
Dependency rejection and TDD without Mocks
Dependency rejection and TDD without MocksDependency rejection and TDD without Mocks
Dependency rejection and TDD without Mocks
 
Using Java to interact with Firebase in Android
Using Java to interact with Firebase in AndroidUsing Java to interact with Firebase in Android
Using Java to interact with Firebase in Android
 
Android Libs - Retrofit
Android Libs - RetrofitAndroid Libs - Retrofit
Android Libs - Retrofit
 

Similar to Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017

Ft10 de smet
Ft10 de smetFt10 de smet
Ft10 de smet
nkaluva
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
ShriKant Vashishtha
 
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Tom Diederich
 

Similar to Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017 (20)

Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21Apache Calcite Tutorial - BOSS 21
Apache Calcite Tutorial - BOSS 21
 
Adopting F# at SBTech
Adopting F# at SBTechAdopting F# at SBTech
Adopting F# at SBTech
 
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
하이퍼커넥트 데이터 팀이 데이터 증가에 대처해온 기록
 
twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇twMVC#46 一探 C# 11 與 .NET 7 的神奇
twMVC#46 一探 C# 11 與 .NET 7 的神奇
 
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVCConstruindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
Construindo APIs de forma produtiva com Spring Boot, Spring Data e Spring MVC
 
Change tracking
Change trackingChange tracking
Change tracking
 
Getting Started with Real-Time Analytics
Getting Started with Real-Time AnalyticsGetting Started with Real-Time Analytics
Getting Started with Real-Time Analytics
 
Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019Rethinking Syncing at AltConf 2019
Rethinking Syncing at AltConf 2019
 
Wcf data services
Wcf data servicesWcf data services
Wcf data services
 
Bringing JAMStack to the Enterprise
Bringing JAMStack to the EnterpriseBringing JAMStack to the Enterprise
Bringing JAMStack to the Enterprise
 
Ft10 de smet
Ft10 de smetFt10 de smet
Ft10 de smet
 
Working effectively with legacy code
Working effectively with legacy codeWorking effectively with legacy code
Working effectively with legacy code
 
meetstore5.pdf
meetstore5.pdfmeetstore5.pdf
meetstore5.pdf
 
Bootiful Development with Spring Boot and React - UberConf 2018
Bootiful Development with Spring Boot and React - UberConf 2018Bootiful Development with Spring Boot and React - UberConf 2018
Bootiful Development with Spring Boot and React - UberConf 2018
 
Bootiful Development with Spring Boot and React - RWX 2017
Bootiful Development with Spring Boot and React - RWX 2017Bootiful Development with Spring Boot and React - RWX 2017
Bootiful Development with Spring Boot and React - RWX 2017
 
Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)Ingesting streaming data for analysis in apache ignite (stream sets theme)
Ingesting streaming data for analysis in apache ignite (stream sets theme)
 
2015 09-02 - transaction log prototype
2015 09-02 - transaction log prototype2015 09-02 - transaction log prototype
2015 09-02 - transaction log prototype
 
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey BuzdinMarvel of Annotation Preprocessing in Java by Alexey Buzdin
Marvel of Annotation Preprocessing in Java by Alexey Buzdin
 
Building a Real-Time Feature Store at iFood
Building a Real-Time Feature Store at iFoodBuilding a Real-Time Feature Store at iFood
Building a Real-Time Feature Store at iFood
 
Spicy javascript: Create your first Chrome extension for web analytics QA
Spicy javascript: Create your first Chrome extension for web analytics QASpicy javascript: Create your first Chrome extension for web analytics QA
Spicy javascript: Create your first Chrome extension for web analytics QA
 

Recently uploaded

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
vu2urc
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 

Recently uploaded (20)

Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
Bajaj Allianz Life Insurance Company - Insurer Innovation Award 2024
 
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdfUnderstanding Discord NSFW Servers A Guide for Responsible Users.pdf
Understanding Discord NSFW Servers A Guide for Responsible Users.pdf
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 
Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024Partners Life - Insurer Innovation Award 2024
Partners Life - Insurer Innovation Award 2024
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 

Data Loading Made Easy with Mike Nakhimovich DroidCon Italy 2017